Polyfill call, apply & bind
Build myCall, myApply, and myBind while comparing strict this, array-like apply arguments, partial binding, and new-aware constructors.
- 01Build call and applyCall a function from an object to set
this, check the list of valuesapplygets, and remove the temporary key after every call. - 02Build bindReturn a wrapper that keeps
this, puts saved values first, and knows when it was called withnew. - 03Compare your helpers with the built-insCheck where the helpers give the same result as the built-in methods: strict functions, primitives,
nameandlength,new, arrow functions, and classes.
Build call, apply, and bind yourself
Inside a function, this is the object the function works on. In the call, apply & bind lesson, you used three built-in methods that every function has. Each one lets you choose this:
callruns the function now. You pass the extra values one by one.applyruns the function now. You pass the extra values in one list.binddoes not run the function. It makes a new function that runs it later.
In this lesson, you write your own copies of all three. Seeing how they work inside makes the rules from The this keyword and Losing this easier to remember.
A polyfill is your own JavaScript code that does the same job as a built-in feature. People write polyfills for old browsers that are missing a feature. Here you write three: myCall, myApply, and myBind. Each one is a normal function on its own. None of them change the built-in methods.
You borrow Ada's phone to make a call. You can dial the numbers one by one. You can pick them from a saved list. Or you can set up speed dial now and make the call later. Every time, it is Ada's phone.
- In real life: Use Ada's phone and dial each number
- In JavaScript:
myCall(fn, receiver, ...args) - In real life: Use Ada's phone and pick numbers from a saved list
- In JavaScript:
myApply(fn, receiver, argsLike) - In real life: Put Ada's phone on speed dial for later
- In JavaScript:
myBind(fn, receiver, ...preset)
This lesson uses ideas from these lessons:
- Methods & this and Strict mode
- Functions are objects and Symbols
- Currying & partial application
- Constructors & new and F.prototype & constructors
Two other lessons also build a built-in yourself: Polyfill array methods and Build a promise from scratch.
myCall: call the function from the object
STEP THROUGHHere is the main trick. When you call a function from an object, like user.label(), JavaScript sets this to that object. The object that becomes this is called the receiver. So myCall does four things:
- It puts the function on the receiver for a moment, under a new key.
- It calls the function from the receiver:
receiver[key](...args). - It deletes the key again.
- It returns what the function returned.
The key is a Symbol. A Symbol is a unique value that you can use as a property key. Press Step to follow the code.
Step through myCall: put the function on the object under a Symbol key, call it from there, delete the key, and return the result.
script
if (typeof fn !== "function") throw new TypeError("Target must be callable"); const receiver = thisArg == null ? globalThis : Object(thisArg); const key = Symbol("myCall"); try { Object.defineProperty(receiver, key, { value: fn, configurable: true }); return receiver[key](...args); } finally { delete receiver[key]; }} function label(prefix, punctuation) { return prefix + this.name + punctuation;}const user = { name: "Ada" };console.log(myCall(label, user, "Hi, ", "!"));Line by line:
- Line 3 picks the receiver.
thisArgis the value you asked to use asthis. If it isnullorundefined, the line usesglobalThis, the global object. OtherwiseObject(thisArg)makes sure it is an object. - Line 4 makes a new
Symbolkey. - Line 6 puts the function on the receiver under that key.
Object.definePropertyadds one property;configurable: truelets us delete it later. - Line 7 calls the function from the receiver, so
thisis the receiver. - Line 9 deletes the key. It is in a
finallyblock, which runs even if the function throws an error.
myCall puts label into the user object under a secret key and calls it from there. Called from user, it gets user as this; then the key is deleted.A string key like "__fn" could replace a property the object already has. Every Symbol("myCall") is new and different from every other key. So it can't clash with a real property.
const user = { name: "Asha", __fn: "keep this value" };
const key = Symbol("temporary function");
user[key] = function () { return this.name; };
console.log(user.__fn, user[key]());
console.log(Reflect.ownKeys(user).includes(key));
delete user[key];
console.log(user.__fn, Object.hasOwn(user, key));- Line 1 makes a user that already has a
__fnproperty. - Line 3 adds a function under a Symbol key.
- Line 4 prints
keep this value Asha. The old__fnvalue is still there, and the function readthis.name. - Line 5 prints
true.Reflect.ownKeyslists all of an object's own keys, and the Symbol is one of them for now. - Line 6 deletes the key. So line 7 prints
keep this value false.Object.hasOwnchecks if the object has a key, and it doesn't any more.
The trick has one weak spot. It must add a property to the receiver, and some objects don't allow that.
function myCall(fn, thisArg) {
const key = Symbol("temporary function");
thisArg[key] = fn;
try { return thisArg[key](); }
finally { delete thisArg[key]; }
}
const user = Object.preventExtensions({ name: "Asha" });
function greet() { return this.name; }
try { console.log(myCall(greet, user)); }
catch (error) { console.log(error.name); }
console.log(greet.call(user));It prints TypeError, then Asha. On line 7, Object.preventExtensions stops anyone from adding new properties to user. So the helper can't add its Symbol key, and the call fails with a TypeError. The built-in call on line 11 doesn't add anything to the object, so it still works.
thisArg: null, undefined, and plain values
REAL OUTPUTthisArg is the first value you pass to call: the value you want as this. What the function really gets depends on one thing: is the function in strict mode? A function is in strict mode when it, or the file around it, starts with "use strict". Code without it is often called sloppy mode.
You also need one more word. A primitive is a plain value that is not an object, like a number or a string. Here are the rules:
- In a sloppy function,
nullandundefinedbecomeglobalThis. A primitive such as5is wrapped in an object, here a Number object. This wrapping is called boxing. - In a strict function,
thisis exactly what you passed.undefinedstaysundefined,nullstaysnull, and5stays the number5.
function sloppyNull() { return this === globalThis;}function sloppyUndefined() { return this === globalThis;}function strictUndefined() { "use strict"; return this === undefined;}function strictNull() { "use strict"; return this === null;}function sloppyPrimitive() { return Object.prototype.toString.call(this);}function strictPrimitive() { "use strict"; return typeof this;} console.log(sloppyNull.call(null));console.log(sloppyUndefined.call(undefined));console.log(strictUndefined.call(undefined));console.log(strictNull.call(null));console.log(sloppyPrimitive.call(5));console.log(strictPrimitive.call(5));The six lines print, in order:
- Lines 23 and 24 print
true. In sloppy functions,nullandundefinedboth becameglobalThis. - Lines 25 and 26 print
true. In strict functions,undefinedandnullstayed as they were. - Line 27 prints
[object Number]. The sloppy function got5boxed in a Number object. - Line 28 prints
number. The strict function got the plain number5.
myCall copies the sloppy rules. Its line 3 uses globalThis for null and undefined, and Object(thisArg) boxes primitives.
But myCall can't copy the strict rule. To set this, it must call the function from an object, and undefined is not an object. So a strict function gets globalThis instead of undefined.
this set to undefined. Native call keeps undefined, but myCall must call from an object, so it uses globalThis.myApply: myCall with a list of values
VALIDATIONapply works like call. The only difference is how you pass the extra values. call takes them one by one. apply takes them in one list, as its second value. That list can be:
nullorundefined, which means "no values".- An array, like
["Ada", "Grace"]. - An array-like object: an object with a
lengthand numbered keys, like{ 0: "Ada", 1: "Grace", length: 2 }.
Anything else, like the number 7, is not a list. Then apply throws a TypeError before the function runs.
function myCall(fn, thisArg, ...args) { const receiver = thisArg == null ? globalThis : Object(thisArg); const key = Symbol("myCall"); try { Object.defineProperty(receiver, key, { value: fn, configurable: true }); return receiver[key](...args); } finally { delete receiver[key]; }}function myApply(fn, thisArg, argsArray) { if (argsArray == null) return myCall(fn, thisArg); if (typeof argsArray !== "object" && typeof argsArray !== "function") { throw new TypeError("CreateListFromArrayLike called on non-object"); } return myCall(fn, thisArg, ...Array.from(argsArray));}function list(a, b) { return this.prefix + a + " & " + b;}const args = { 0: "Ada", 1: "Grace", length: 2 };console.log(myApply(list, { prefix: "Team: " }, args));- Line 12 handles
nullandundefined. It callsmyCallwith no extra values. - Lines 13 to 15 throw a
TypeErrorif the list is not an object or a function. - Line 16 uses
Array.fromto turn the array-like object into a real array. The...(spread) then passes its items tomyCallone by one. - Line 22 prints
Team: Ada & Grace.
Today, when you just need to pass an array's items one by one, spread is easier to read than apply:
const scores = [4, 9, 2];console.log(Math.max.apply(null, scores));console.log(Math.max(...scores));Both lines print 9.
myBind: save this and the first values
PARTIAL APPLICATIONbind does not run the function. It returns a new function, called a wrapper, that runs the original later. When you call the wrapper:
- It uses the
thisyou gave tobind. - It passes the values you gave to
bindfirst, then the values from the call.
Saving the first values ahead of time is called partial application. The currying lesson covers it in more detail.
function multiply(a, b, c) { return a * b * c;}const nativeDouble = multiply.bind(null, 2);console.log(nativeDouble.name);console.log(nativeDouble.length);console.log(nativeDouble(3, 4)); function showName(punctuation) { return this.name + punctuation;}const first = showName.bind({ name: "Ada" });const rebound = first.bind({ name: "Grace" });console.log(rebound("!"));Lines 4 to 7 use the built-in bind:
- Line 4 saves
2as the first value.nullis thethis, whichmultiplydoesn't use. - Line 5 prints
bound multiply. A bound function'snamestarts with"bound ". - Line 6 prints
2. A function'slengthis how many parameters it lists.multiplyhas 3, and one is already filled, so 2 are left. - Line 7 prints
24, because2 * 3 * 4is 24.
bind saved 2. Calling nativeDouble(3, 4) puts 2 first, then 3 and 4, so multiply returns 2 * 3 * 4, which is 24.Lines 12 to 14 bind a function twice. Line 12 binds Ada. Line 13 tries to bind Grace. Line 14 prints Ada!. The first bound this stays. A second bind can add more values, but it can't change this.
This lesson's myBind also tries to set name and length the same way, with Object.defineProperty. Some older JavaScript engines (the programs that run your code) don't allow that. Then the helper skips it, and the wrapper still works.
Be careful with length. It stops counting at the first parameter with a default value, like punctuation = "!", or at a rest parameter, like ...items. So don't use it to check how a function should be called.
function greet(name, punctuation = "!") {
return "Hi, " + name + punctuation;
}
const greetAsha = greet.bind(null, "Asha");
console.log(greetAsha.name);
console.log(greetAsha.length);Line 5 prints bound greet. Line 6 prints 0. greet.length is 1, because counting stops at punctuation, which has a default. Binding name takes away that one, so 0 are left. It never goes below 0.
bind with new
STEP THROUGHYou can also call a bound function with new. new calls a function as a constructor: it makes a fresh object and runs the function with that object as this. When you use new on a bound function:
- The bound
thisis not used. The fresh object isthisinstead. - The saved values still come first.
- The fresh object is still a
Point, sopoint instanceof Pointistrue.instanceofchecks if an object was made from a constructor's prototype.
Simple bind helpers often get this case wrong.
You have a paper form called Point with two blanks. Someone has already written 1 in the first blank. Each time you need a point, you take a fresh copy of the form and fill in the second blank. The copy is still a Point form.
- In real life: Each copy is still a Point form
- In JavaScript:
point instanceof Pointistrue - In real life: The first blank already says 1
- In JavaScript: The saved
1becomesx - In real life: You take a fresh copy of the form
- In JavaScript:
newmakes a fresh object forthis
Step through myBind with new: new.target tells the helper to use the fresh object as this.
script
function boundFunction(...later) { const usingNew = new.target !== undefined; const args = [...preset, ...later]; if (usingNew) { return Reflect.construct(fn, args, new.target === boundFunction ? fn : new.target); } return fn.apply(thisArg, args); } return boundFunction;} function Point(x, y) { this.x = x; this.y = y;}Point.prototype.label = function () { return this.x + "," + this.y;}; const BoundPoint = myBind(Point, { x: 99, y: 99 }, 1);const point = new BoundPoint(2);console.log(point.label());console.log(point instanceof Point);Press Step to follow new BoundPoint(2). Two tools do the work:
new.targetis set only when a function is called withnew. Line 3 uses it to check.Reflect.construct(fn, args, newTarget)runsfnwithnewand the values inargs. Line 6 uses it, soPointruns as a constructor and the bound object is never used.
new, the bound object { x: 99, y: 99 } is skipped. A fresh point gets the saved 1 as x and the 2 from the call as y.Before new.target existed, bind polyfills used a different trick. They made an empty helper function, Empty, and gave it Point's prototype. Then this instanceof Empty told them if new was used:
function classicBind(fn, thisArg, ...preset) { function Empty() {} function bound(...later) { const receiver = this instanceof Empty ? this : thisArg; return fn.apply(receiver, preset.concat(later)); } if (fn.prototype) { Empty.prototype = fn.prototype; bound.prototype = new Empty(); // Keep Empty.prototype intact because bound uses instanceof Empty. } return bound;} function Point(x, y) { this.x = x; this.y = y;}const BoundPoint = classicBind(Point, { x: 99 }, 1);const point = new BoundPoint(2);console.log(point instanceof Point);- Line 2 makes the empty helper function.
- Line 4 checks: was this call made with
new? If so, it uses the fresh object. If not, it usesthisArg. - Lines 8 and 9 link the prototypes, so an object made by
boundalso counts as aPoint. - Line 20 calls it with
new, and line 21 printstrue.
Two more cases show why Reflect.construct is the better tool:
class Member {
constructor(name) { this.name = name; }
}
function myBind(fn, thisArg, ...preset) {
function boundFunction(...later) {
const args = [...preset, ...later];
if (new.target) {
return Reflect.construct(fn, args, new.target === boundFunction ? fn : new.target);
}
return fn.apply(thisArg, args);
}
return boundFunction;
}
const BoundMember = myBind(Member, { name: "ignored" }, "Asha");
const member = new BoundMember();
console.log(member.name, member instanceof Member);
function Profile(name) {
this.name = name;
return { name: "Returned" };
}
const BoundProfile = myBind(Profile, null, "Asha");
const profile = new BoundProfile();
console.log(profile.name, profile instanceof Profile);- Line 16 prints
Asha true.Memberis a class, and a class only runs withnew.Reflect.constructruns it withnew, skips the bound object, and makes a realMember. - Line 24 prints
Returned false.Profilereturns its own object on line 20. Withnew, a returned object takes the place of the fresh one. Soprofileis that returned object, not aProfile.
Calling a constructor with apply can't do either of these.
Built-in vs polyfill: what matches
COMPAREPeople call the built-in methods native. A polyfill is only useful if you know where it matches the native method and where it doesn't. The table lists each case. The lab below it runs both and shows the real values. The biggest difference is the strict undefined case from earlier.
Read each row as: in this case, does the helper give the same result as the native method, and why?
| Case | Does it match? | Why |
|---|---|---|
call with an object as this | Yes. | Calling from the object gives the same this. |
null or undefined in a sloppy function | Yes. | Both use globalThis instead. |
null or undefined in a strict function | No. | The built-in passes the exact value. The Symbol trick must call from an object, so it can't. |
A primitive, like 5, as this | Yes, for sloppy functions. | Both box it in an object; the helper uses Object(thisArg). A strict function gets a box from the helper, not the plain value. |
apply with a null list | Yes. | Both run the function with no extra values. |
apply with a number as the list | Yes, same error. | Both throw a TypeError; the helper checks before Array.from. |
bind with saved values | Yes. | Saved values go first, then the values from the call. |
| Binding twice | Yes. | The first bound this stays; a second bind can only add values. |
new with a bound constructor | Yes, in this helper. | Reflect.construct skips the bound object, keeps the prototype, and works with classes. |
| A constructor that returns an object | Yes, in this helper. | Like the built-in new, the returned object takes the place of the fresh one. |
name and length | Usually. | The built-in sets them; the helper tries with Object.defineProperty, where the engine allows it. |
A class with call | Yes, both fail. | A class only runs with new, so both throw. |
// All helpers are standalone; no built-in prototype is patched.const cases = [ "normal object receiver", "strict undefined receiver", "primitive receiver", "array-like apply arguments", "partial bind", "bind with new", "name and length", "class constructor call",];for (const label of cases) { console.log(label);}- Built-in
Hello, Ada- Helper
Hello, Ada- Verdict
- same result
Calling the function from the object gives the same this as the built-in call.
An arrow function has no this of its own. It uses the this from the code around it. So call, apply, and bind can't change it. A class is the other way around: it only runs with new. Calling a class with call throws a TypeError.
const arrow = (function makeArrow() { return () => this.name;}).call({ name: "Ada" });console.log(arrow.call({ name: "Grace" })); class Widget {}try { Widget.call({});} catch (error) { console.log(error.name); console.log(error.message);}- Line 4 prints
Ada, not Grace. The arrow made on line 2 keeps thethisfrom line 3. - Lines 10 and 11 print
TypeErrorand its message, which says the class can't run withoutnew.
Where you’ll use this
SORT ITYou may never ship these helpers. But the same ideas show up in everyday code:
- Event handlers use
bind, or a wrapper function, to keep their object asthis. - Libraries borrow array methods, like
join, and use them on array-like objects. - Helper code uses
bindto save a value that repeats, like a log prefix, a tax rate, or a language setting.
- Use
Array.prototype.joinon an array-like object right now - Run a function with values already stored in
{ 0: "Ada", length: 1 } - Make a function that keeps
viewasthisfor a click that happens later - Save
0.08as the first value ofpriceWithTax(rate, price) - Read older code that does
Math.max.apply(null, numbers) - Support
new BoundPoint(2)after saving1forx
Sort each task by the helper it needs.
const user = {
name: "Asha",
greeting() { return "Hello, " + this.name; },
};
const showGreeting = user.greeting.bind(user);
console.log(showGreeting());It prints Hello, Asha. Suppose you passed user.greeting on its own to other code. When that code calls it later, it has lost user. Line 5 uses bind to make a function that keeps user as this.
In real apps, use the built-in call, apply, and bind. They handle strict functions, constructors, and the other cases correctly. The helpers in this lesson are for learning. If a very old browser needs a polyfill, use a well-tested polyfill library, not these snippets.
Modern JavaScript often has a clearer way to do the same job:
- Write
Math.max(...numbers)instead ofMath.max.apply(null, numbers). - A small arrow wrapper, like
() => user.greeting(), is often easier to read thanbind. - Use
Object.hasOwn(obj, key)instead of borrowinghasOwnPropertywithcall, when the browsers you support have it.
Common misconceptions
- “The Symbol trick is how JavaScript really does it.” No. It shows the idea of calling from an object. The built-in method works inside the engine and adds no property.
- “Strict and sloppy functions get the same
this.” No. Strict functions get exactly what you passed. Sloppy functions swapnullandundefinedforglobalThis, and box primitives. - “
applytakes any list-like value.” No. Nativeapplywants an array or an array-like object: something with alengthand numbered keys. This lesson's helper checks the value first, then usesArray.from. - “
bindonly setsthis.” No. It also saves the first values, and you can use the bound function withnew. - “Binding twice changes
thisagain.” No. The first boundthisstays. - “A bound constructor writes into the bound object.” No.
newmakes a fresh object and skips the bound one. - “A Symbol key makes the helper complete.” No. The Symbol stops key clashes. But the helper still fails on objects that can't get new properties, and it can't give a strict function
undefinedasthis. - “A function's
lengthshows all its parameters.” No. It stops counting at the first default value or rest parameter. - “A class is just a function you can
call.” No. A class throws aTypeErrorunless you usenew.
Read each row across: when the tool runs, what you pass it, and how this lesson builds it.
| Tool | When does it run? | What do you pass? | How does this lesson build it? |
|---|---|---|---|
call | It runs the function right away. | First the this value, then the other values one by one. | myCall puts the function on the object under a Symbol key and calls it from there. |
apply | It runs the function right away. | First the this value, then one list: null, undefined, an array, or an array-like object. | myApply checks the list, turns it into an array, and uses myCall. |
bind | It doesn't run it. It returns a new function for later. | First the this value to keep, then the values to put first. | myBind returns a wrapper, tries to set name and length, and handles new. |
| Arrow functions | It runs when you call it, like any function. | call, apply, and bind can pass it values, but can't change its this. | There is nothing to build, because its this can't be changed. |
Practice exercises
5 EXERCISESFill in the helper so the last line prints Hi, Ada!.
function myCall(fn, thisArg, ...args) {
// 1. Pick the receiver: globalThis for null or undefined, otherwise Object(thisArg).
// 2. Put fn on it under a Symbol key, call receiver[key](...args), then delete the key.
}
function greet(punctuation) {
return "Hi, " + this.name + punctuation;
}
console.log(myCall(greet, { name: "Ada" }, "!"));function myCall(fn, thisArg, ...args) {
if (typeof fn !== "function") throw new TypeError("Target must be callable");
const receiver = thisArg == null ? globalThis : Object(thisArg);
const key = Symbol("myCall");
try {
Object.defineProperty(receiver, key, { value: fn, configurable: true });
return receiver[key](...args);
} finally {
delete receiver[key];
}
}
function label(prefix, punctuation) {
return prefix + this.name + punctuation;
}
const user = { name: "Ada" };
console.log(myCall(label, user, "Hi, ", "!"));The function is called from the receiver, so JavaScript uses the receiver as this. Then finally deletes the key.
The starter calls Array.from(null), which throws. Fix it so apply works with no list.
function brokenApply(fn, thisArg, argsArray) {
return fn.call(thisArg, ...Array.from(argsArray));
}
function who() { return this.name; }
try {
console.log(brokenApply(who, { name: "Ada" }, null));
} catch (error) {
console.log(error.name);
}function myCall(fn, thisArg, ...args) {
const receiver = thisArg == null ? globalThis : Object(thisArg);
const key = Symbol("myCall");
try {
Object.defineProperty(receiver, key, { value: fn, configurable: true });
return receiver[key](...args);
} finally {
delete receiver[key];
}
}
function myApply(fn, thisArg, argsArray) {
if (argsArray == null) return myCall(fn, thisArg);
return myCall(fn, thisArg, ...Array.from(argsArray));
}
function who() { return this.name; }
console.log(myApply(who, { name: "Ada" }, null));The fixed helper handles the empty case first. It only calls Array.from when there is a real list.
Add the check so myApply(noop, null, 7) throws, and the program prints the error's name.
function myApply(fn, thisArg, argsArray) {
if (argsArray == null) return myCall(fn, thisArg);
// Add the check the built-in apply makes: throw a TypeError if the list is not an object.
}
function noop() { return "ok"; }
try {
myApply(noop, null, 7);
} catch (error) {
console.log(error.name);
}function myCall(fn, thisArg, ...args) {
const receiver = thisArg == null ? globalThis : Object(thisArg);
const key = Symbol("myCall");
try {
Object.defineProperty(receiver, key, { value: fn, configurable: true });
return receiver[key](...args);
} finally {
delete receiver[key];
}
}
function myApply(fn, thisArg, argsArray) {
if (argsArray == null) return myCall(fn, thisArg);
if (typeof argsArray !== "object" && typeof argsArray !== "function") {
throw new TypeError("CreateListFromArrayLike called on non-object");
}
return myCall(fn, thisArg, ...Array.from(argsArray));
}
function noop() { return "ok"; }
try {
myApply(noop, null, 7);
} catch (error) {
console.log(error.name);
}This matches the native rule: the list can be missing, null, undefined, or an object, but not a number.
Finish the wrapper in myBind so double(6) uses the saved 2 and prints 12.
function myBind(fn, thisArg, ...preset) {
return function boundFunction(...later) {
// Call fn with thisArg. Pass the preset values first, then the later ones.
};
}
function multiply(a, b) { return a * b; }
const double = myBind(multiply, null, 2);
console.log(double(6));function myBind(fn, thisArg, ...preset) {
return function boundFunction(...later) {
return fn.apply(thisArg, preset.concat(later));
};
}
function multiply(a, b) { return a * b; }
const double = myBind(multiply, null, 2);
console.log(double(6));The wrapper saves 2 as the first value. Calling double(6) passes [2, 6] to multiply, which returns 12.
Finish the new case so new BoundPoint(2) prints 1,2 true.
function myBind(fn, thisArg, ...preset) {
function boundFunction(...later) {
const receiver = new.target ? this : thisArg;
return fn.apply(receiver, preset.concat(later));
}
// Link boundFunction.prototype to fn.prototype here.
return boundFunction;
}
function Point(x, y) { this.x = x; this.y = y; }
Point.prototype.label = function () { return this.x + "," + this.y; };
const BoundPoint = myBind(Point, { x: 99 }, 1);
const point = new BoundPoint(2);
try {
console.log(point.label(), point instanceof Point);
} catch (error) {
console.log(error.name);
}function myBind(fn, thisArg, ...preset) {
function boundFunction(...later) {
const receiver = new.target ? this : thisArg;
const result = fn.apply(receiver, preset.concat(later));
return result;
}
if (fn.prototype) {
boundFunction.prototype = Object.create(fn.prototype);
}
return boundFunction;
}
function Point(x, y) { this.x = x; this.y = y; }
Point.prototype.label = function () { return this.x + "," + this.y; };
const BoundPoint = myBind(Point, { x: 99 }, 1);
const point = new BoundPoint(2);
console.log(point.label(), point instanceof Point);With new, the wrapper skips the bound object and passes the saved and new values to the fresh object. The prototype link keeps point instanceof Point true.
Check your understanding
8 QUESTIONSQuestion 1 of 8Why does
myCallput the function on the object under aSymbolkey before calling it?Choose an answer to see the explanation.
Question 2 of 8What do these strict and sloppy checks print?
Read the code, then predictfunction sloppy() { return this === globalThis; } function strict() { "use strict"; return this === undefined; } console.log(sloppy.call(null)); console.log(strict.call(undefined));Choose an answer to see the explanation.
Question 3 of 8What does the built-in apply print when the list is a number?
Read the code, then predictfunction add(a, b) { return a + b; } try { console.log(add.apply(null, 7)); } catch (error) { console.log(error.name); }Choose an answer to see the explanation.
Question 4 of 8What does the bound multiplier print?
Read the code, then predictfunction multiply(a, b) { return a * b; } const double = multiply.bind(null, 2); console.log(double(6));Choose an answer to see the explanation.
Question 5 of 8What does binding the same function twice print?
Read the code, then predictfunction show() { return this.name; } const once = show.bind({ name: "Ada" }); const twice = once.bind({ name: "Grace" }); console.log(twice());Choose an answer to see the explanation.
Question 6 of 8What does this bound constructor code print?
Read the code, then predictfunction Point(x, y) { this.x = x; this.y = y; } Point.prototype.label = function () { return this.x + "," + this.y; }; const BoundPoint = Point.bind({ x: 99, y: 99 }, 1); const point = new BoundPoint(2); console.log(point.label()); console.log(point instanceof Point);Choose an answer to see the explanation.
Question 7 of 8What does calling a class with
callprint here?Read the code, then predictclass Widget {} try { Widget.call({}); } catch (error) { console.log(error.name); console.log(error.message.includes("cannot be invoked without 'new'")); }Choose an answer to see the explanation.
Question 8 of 8What
nameandlengthdoes the built-in bind give here?Read the code, then predictfunction multiply(a, b, c) { return a * b * c; } const bound = multiply.bind(null, 2); console.log(bound.name); console.log(bound.length);Choose an answer to see the explanation.
Key takeaways
myCallputs the function on the object under a newSymbolkey, calls it from there, then deletes the key.myApplyismyCallplus two steps: check the list, then turn it into an array withArray.from.- The Symbol trick can't give a strict function
undefinedasthis. That is where it differs most from the built-in. myBindreturns a wrapper. The wrapper setsthis, puts the saved values first, and usesnew.targetto handlenew.name,length, arrow functions, classes, and constructors that return objects are the cases that make a full polyfill hard.
Remember the one-liner.call and apply call the function from the object now; bind makes a wrapper that does it later.
Coming next: Build a promise from scratch.