Command Palette

Search for a command to run...

Hectal
PHASE 7Intermediate ~25 min· topic 2 of 8

Topic 7.2

call, apply and bind

In one line

Every function has three methods that let you choose this yourself: call runs the function now with arguments listed one by one, apply runs it now with arguments in an array, and bind doesn't run it at all but returns a new function whose this (and optionally first arguments) are fixed for good.

Think of it like this

A rubber stamp that says "Paid by ___". **call is stamping a bill right now and writing the name in the blank: "Paid by Asha". apply is the same, but the details arrive on a list you hand over. bind is ordering a new stamp** with "Asha" already carved into the blank: you don't stamp anything yet, but every later use of that stamp says "Asha", and nobody can rub it out.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Explicit binding
Choosing this yourself with call, apply or bind, instead of letting the call site decide.
thisArg
The first argument of call, apply and bind: the value this should be.
call
Runs a function right now with a chosen this and arguments listed one by one.
apply
Runs a function right now with a chosen this and the arguments in one array.
bind
Makes a new function with this (and maybe some arguments) fixed forever. It doesn't run anything.
Bound function
The new function bind returns. Its this can't be changed by how it's called (only new overrides it).
Partial application
Fixing some of a function's arguments now, and giving the rest later.
Method borrowing
Running a method that belongs to one kind of object on a different object, using call or apply.
Array-like
An object with a length and numbered keys (0, 1, ...) that isn't a real array, like a string or arguments.

Step by step

01A function that needs this

introduce reads this.name, but no object owns it. Called plainly in strict mode it would fail. We want to run it for Asha without adding it to her object.

intro.jswhole filejavascript
"use strict";
function introduce(greeting, mark) {
  return greeting + ", I am " + this.name + mark;
}
const asha = { name: "Asha" };

02call: run it now, arguments with commas

call puts asha in the this slot and passes the rest as normal arguments.

intro.jswhole filejavascript
introduce.call(asha, "Hello", "!"); // "Hello, I am Asha!"

03apply: run it now, arguments in an array

Same result, but the arguments travel together in an array. Useful when they already are an array.

intro.jswhole filejavascript
const args = ["Hi", "."];
introduce.apply(asha, args);      // "Hi, I am Asha."
introduce.call(asha, ...args);    // the modern way to do the same

04bind: make a new function for later

bind returns a function and runs nothing. Here this and the first argument are fixed; the second argument comes later.

The new function is a real function you can store, pass to setTimeout, or use as an event listener.

intro.jswhole filejavascript
const ashaSaysHey = introduce.bind(asha, "Hey");
ashaSaysHey("!!");   // "Hey, I am Asha!!"
ashaSaysHey("?");    // "Hey, I am Asha?"
bind: make a new function for laterdiagram
Rendering diagram…

05A bound function can't be re-bound

Binding again, using call, or attaching the bound function to another object all leave this as asha. The first bind wins forever.

This is why bound functions are safe to pass around, and also why you can't "fix" a function someone else already bound.

intro.jswhole filejavascript
const ravi = { name: "Ravi" };
ashaSaysHey.call(ravi, "!");          // still "Hey, I am Asha!"
ashaSaysHey.bind(ravi)("!");          // still Asha

Try it yourself

  1. 1

    Bind with new arguments

    In the first example, add const raviAsks = introduce.bind(ravi, "Excuse me"); and log raviAsks("?"). Predict the text before running.

  2. 2

    Remove the wrong function

    In the second example, change button.removeEventListener("click", handler); to button.removeEventListener("click", counter.increment.bind(counter));. Predict the last count. Why didn't the listener go away?

  3. 3

    Try call on an arrow

    Add const arrow = () => this; console.log(arrow.call({ x: 1 }) === window); to the third example. Predict true or false, then check.

Code & diagrams

call, apply and bind side by side New tab

A bound function's `name` starts with `bound `. The last two lines tried to switch to Ravi and failed: the first `bind` is permanent.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Hello, I am Asha!
Hi, I am Ravi.
function bound introduce
Hey, I am Asha!!
Hey, I am Asha?
Hey, I am Asha?
bind fixes lost this in a listener New tab

Keep the bound function in a variable: `removeEventListener` needs the **same** function. Calling `bind` again would make a different one, and nothing would be removed.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

count: 2
after removing, count: 2
Partial application and borrowing methods New tab

`double.length` is 1 because one of the two parameters is already filled. `apply` and spread give the same answer; spread is the modern way.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

10 15 length: 1
[ 'A', 'B', 'C' ]
tea + coffee
[object Array] [object Null] [object Date]
9 9

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Pass the result of call instead of a function

Write button.addEventListener("click", counter.increment.call(counter));.

terminal
$ the page shows
── what you'll see ──
The count goes to 1 straight away, and clicking the button does nothing.

Break #2

Bind inside removeEventListener

Add with el.addEventListener("click", obj.go.bind(obj)) and remove with el.removeEventListener("click", obj.go.bind(obj)).

terminal
$ the page shows
── what you'll see ──
The listener still runs after it was "removed".

Myth vs fact

Myth

bind changes the original function.

Fact

It returns a brand-new function and leaves the original untouched. obj.method still loses this when detached; only the bound copy is safe.

Myth

apply is faster than call, or the other way round.

Fact

In modern engines the difference is negligible. Pick by how your arguments arrive; in new code spread syntax usually replaces apply.

Myth

You can bind an arrow function to an object.

Fact

arrow.bind(obj) returns a function, but its this is still the one from where the arrow was written. Only the extra arguments are fixed.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A bound function is a special bound function exotic object with internal slots [[BoundTargetFunction]], [[BoundThis]] and [[BoundArguments]]. It has no prototype property of its own; new bound() constructs the target function, ignores [[BoundThis]], and still prepends the bound arguments.

  • ▸

    fn.length of a bound function is max(0, target.length - boundArgs.length) and its name is "bound " + target.name. Stack traces and DevTools show that name, which helps when debugging.

  • ▸

    Engines optimise apply with an array literal and call well, but apply with a very large array (hundreds of thousands of items) can throw RangeError: Maximum call stack size exceeded, because every item becomes a separate argument on the stack. The same limit hits spread: Math.max(...hugeArray). Use a loop or reduce for huge arrays.

  • ▸

    If someone may have overwritten a method on an object (or the object has a null prototype), call the original from the prototype: Object.prototype.hasOwnProperty.call(obj, key). ES2022's Object.hasOwn(obj, key) exists to replace exactly this pattern.

Remember this

  1. 1

    Functions in JavaScript are objects, and every function inherits three methods from Function.prototype (you'll see what that means in Topic 7.3): call, apply and bind. Each takes as its first argument the value you want this to be. This is called explicit binding, because you say what this is instead of letting the call site decide.

  2. 2

    **fn.call(thisArg, a, b)** calls fn immediately, with this set to thisArg and the remaining arguments passed one by one. greet.call(asha, "Hello", "!") works exactly like asha.greet("Hello", "!") would, even though asha has no greet property. It returns whatever the function returns.

  3. 3

    **fn.apply(thisArg, [a, b]) is the same, but the arguments come as one array (or array-like object). Mnemonic: apply takes an array, call takes c**ommas. Before spread syntax (ES2015) this was the only way to pass an array as separate arguments: Math.max.apply(null, nums). Today Math.max(...nums) does the same and reads better, so apply is mostly seen in older code.

  4. 4

    **fn.bind(thisArg, a) does not** call fn. It returns a new bound function: calling it later calls fn with this fixed to thisArg and with a placed before any new arguments. The binding is permanent: calling the bound function as a method, through call, or binding it again can't change this. Only new overrides it (then this is the new object). That makes bind the classic fix for lost this: button.addEventListener("click", counter.increment.bind(counter)).

  5. 5

    bind also does partial application: fixing some arguments now and supplying the rest later. const double = multiply.bind(null, 2) makes a function that always passes 2 as the first argument, so double(5) is 10. Pass null as thisArg when the function doesn't use this.

  6. 6

    Borrowing methods is the other big use. Many built-in methods work on anything that looks right, so you can run them on an object that doesn't own them: Array.prototype.map.call("abc", (c) => c.toUpperCase()) maps over a string's characters, and Object.prototype.toString.call(value) gives a reliable type tag like [object Array]. Arrow functions ignore all three: their this comes from where they were written, so arrow.call(obj) passes the arguments but leaves this alone.

Explain it without notes

01

What is the difference between call, apply and bind?

02

Why can't a bound function's this be changed, and is there any exception?

03

Write a simple version of Function.prototype.bind and explain it.

04

What is method borrowing? Give two real examples.

Practice

01

Use call to run function describe(unit) { return this.amount + " " + unit; } for { amount: 3 } with the unit "kg", and apply to do the same with { amount: 5 } and ["litres"].

02

Make const addTax = applyRate.bind(null, 0.18); from function applyRate(rate, price) { return price + price * rate; } and print addTax(100) and addTax(250).

03

Write logAll() that prints Math.min of its own arguments by borrowing: use Math.min.apply with the arguments object, and then the modern rest-parameter version.

Trade-offs

  • ↔

    bind gives a stable, reusable function (good for add/remove listener pairs), but each call allocates a new function; an inline arrow () => obj.m() is just as clear for one-off callbacks.

  • ↔

    Borrowing prototype methods works on any array-like, but Array.from(x) or spread make an honest array first and are easier to read; borrow only when copying would be wasteful.

Done when you can

  • I can use call and apply to run a function with a chosen this.

  • I can use bind to make a function with this fixed, and keep it for removeEventListener.

  • I can use bind for partial application.

  • I know a bound function can't be re-bound and arrows ignore all three.

  • I can borrow array and object methods with call.