call, apply and bind
Setting this by hand — call and apply invoke now, bind returns a new function with this and leading arguments fixed for good.
After this lesson you can
- Choose between call, apply and bind for a given job
- Use bind for partial application
- Explain why a bound function cannot be re-bound and why arrows ignore call
- Write a wrapper that forwards this and its arguments with apply
Lesson 4 listed explicit binding as the second of the four rules for
this. Three methods every function has implement it.
function introduce(greeting, punctuation) {
return `${greeting}, I am ${this.name}${punctuation}`;
}
const ana = { name: "Ana" };
introduce.call(ana, "Hi", "!"); // calls now, arguments listed
introduce.apply(ana, ["Hi", "!"]); // calls now, arguments as an array
const hi = introduce.bind(ana, "Hi"); // calls nothing, returns a function
hi("?"); // "Hi, I am Ana?"
call(thisArg, ...args)invokes the function once with thatthis.apply(thisArg, argsArray)is the same with the arguments in an array. Before spread syntax it was how you passed an array as arguments (Math.max.apply(null, nums)); todayMath.max(...nums)does the same.bind(thisArg, ...args)invokes nothing. It returns a new function withthisfixed and the given arguments pre-filled in front of whatever the caller passes later.
Partial application
Pre-filling leading arguments is useful on its own, even when this
does not matter (pass null):
const add = (a, b) => a + b;
const addTen = add.bind(null, 10);
[1, 2, 3].map((n) => addTen(n)); // [11, 12, 13]
Bound is bound for good
A bound function ignores any later attempt to change its this — a
second bind, call or apply can add arguments but cannot replace the
this that the first bind fixed. Arrow functions go further: they
have no this of their own, so call, apply and bind cannot give
them one either.
function whoAmI(...args) { return `${this.name} [${args.join(",")}]`;} export function run() { const a = { name: "A" }; const b = { name: "B" }; const boundToA = whoAmI.bind(a, 1); const outer = { name: "outer" }; const arrow = function () { return () => this === outer; }.call(outer); return { bound: boundToA(2), reboundToB: boundToA.bind(b, 3)(4), calledWithB: boundToA.call(b, 5), arrowGivenThis: (() => this?.name ?? "no name").call(b), arrowFromOuter: arrow(), sameFunction: whoAmI.bind(a) === whoAmI.bind(a), };}Borrowing methods
call lets one object use another's method. A classic case is an
array-like value — something with length and numeric keys but no array
methods, such as arguments or a DOM NodeList:
function collect() { return { isArray: Array.isArray(arguments), doubled: Array.prototype.map.call(arguments, (n) => n * 2), viaFrom: Array.from(arguments).reverse(), };} export function run() { const arrayLike = { 0: "x", 1: "y", length: 2 }; return { fromArguments: collect(1, 2, 3), positionOfY: Array.prototype.indexOf.call(arrayLike, "y"), maxViaApply: Math.max.apply(null, [3, 9, 4]), maxViaSpread: Math.max(...[3, 9, 4]), };}Array.from(arrayLike) is usually the clearer way to get a real array,
but recognising Array.prototype.map.call(...) in older code is worth
it.
Wrappers forward this with apply
Logging, timing and retry helpers wrap a function in another one. If
the wrapped function is a method, the wrapper must hand on the this
it was called with — so it has to be a regular function, which gets
a this from its call site, and it calls the original with
fn.apply(this, args). An arrow wrapper would use the this of the
place it was written, and fn(...args) would call the method with no
receiver at all. The exercise builds one.
Common mistakes
- Calling the result of
bindtoo early.button.onclick = handler.bind(obj)()runs the handler immediately and assigns its return value;bindalready returns the function you want. - Re-binding to "fix"
this.f.bind(a).bind(b)still usesa. - Expecting
callto change an arrow function'sthis. It has no effect; the arrow keeps the of the scope it was written in.
Exercise · Pro
A logging wrapper that keeps this
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade