bind() vs call() vs apply() in JavaScript: The little-known difference

Every developer should fully understand how they work and be able to discern the subtle differences between them.

So you know, JS functions are first-class citizens.

Which means: they’re all just object values — all instances of the Function class, with methods and properties.

So bind(), apply(), and call() are 3 essential methods every JavaScript function has.

Were you with me in the painful early days of React; when we still did this? 👇

This was just one of the multiple applications of bind() — a seriously underrated JavaScript method.

JavaScript
// damn class components are such a chore to write now import React from 'react'; class MyComponent extends React.Component { constructor(props) { super(props); } greet() { alert(`Hi, I'm ${this.props.name}!`); } // remember render()? render() { return ( <button onClick={this.greet.bind(this)}>Click me</button> ); } } export default MyComponent;

sayName() would be a mess without bind() — the alert() would never work.

Because internally React is doing something fishy with this method that completely screws up what this means inside it.

Before sayName would have had absolutely no problems showing the alert — just like in this other class:

JavaScript
class Person { props = { name: 'Tari' }; greet() { console.log(`Hi, I'm ${this.props.name}!`); } } const person = new Person(); person.greet();

But guess what React does to the greet event handler method behind the scenes?

It reassigns it to another variable:

JavaScript
class Person { props = { name: 'Tari' }; greet() { console.log(`Hi, I'm ${this.props.name}!`); } } // reassign to another variable: const greet = Person.prototype.greet; // ❌ bad idea greet();

So guess what happens to this — it’s nowhere to be found:

This is where bind comes to the rescue — it changes this to any instance object you choose:

So we’ve binded the function to the object — the bind target.

(I know it’s “bound” but let’s say binded just like how we say “indexes” for “index” instead of “indices”).

It’s immutable — it returns the binded function without changing anything about the original one.

And this lets us use it as many times as possible:

vs call()

There’s only a tiny difference between call and bind

bind creates the binded function for you to use as many times as you like.

But call? It creates a temporary binded function on the fly and calls it immediately:

JavaScript
class Person { constructor(props) { this.props = props; } greet() { console.log(`Hi, I'm ${this.props.name}`); } } const person = new Person({ name: 'Tari' }); const greet = Person.prototype.greet; greet.bind(person)(); greet.call(person);

So call() is basically bind() + a call.

But what about when the function has arguments? What do we do then?

No problem at all — just pass them as more arguments to call:

JavaScript
class Person { constructor(props) { this.props = props; } greet(name, favColor) { console.log( `Hi ${name}, I'm ${this.props.name} and I love ${favColor}` ); } } const person = new Person({ name: 'Tari' }); const greet = Person.prototype.greet; // bind(): normal argument passing to binded function greet.bind(person)('Mike', 'blue🔵'); // call(): pass as more arguments greet.call(person, 'Mike', 'blue🔵');

And you can actually do the same with bind():

JavaScript
// the same thing greet.bind(person)('Mike', 'blue🔵'); greet.bind(person, 'Mike', 'blue🔵')();

vs apply()

At first you may think apply() does the exact same thing as call():

JavaScript
class Person { constructor(props) { this.props = props; } greet() { console.log(`Hi, I'm ${this.props.name}`); } } const person = new Person({ name: 'Tari' }); const greet = Person.prototype.greet; greet.call(person); // Hi, I'm Tari greet.apply(person); // Hi, I'm Tari

But just like bind() vs call() there’s a subtle difference to be aware of:

Arguments passing:

JavaScript
class Person { constructor(props) { this.props = props; } greet(name, favColor) { console.log( `Hi ${name}, I'm ${this.props.name} and I love ${favColor}` ); } } const person = new Person({ name: 'Tari' }); const greet = Person.prototype.greet; //💡call() -- pass arguments with comma separated greet.call(person, 'Mike', 'blue🔵'); // Hi, I'm Tari //💡apply() -- pass arguments with array greet.apply(person, ['Mike', 'blue🔵']); // Hi, I'm Tari

One mnemonic trick I use to remember the difference:

  • call() is for commas
  • apply() is for arrays

Recap

  • bind() — bind to this and return a new function, reusable
  • call() — bind + call function, pass arguments with commas
  • apply() — bind + call function, pass arguments with array


Every Crazy Thing JavaScript Does

A captivating guide to the subtle caveats and lesser-known parts of JavaScript.

Every Crazy Thing JavaScript Does

Sign up and receive a free copy immediately.

Leave a Comment

Your email address will not be published. Required fields are marked *