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

Last updated on August 15, 2024
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.

// 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:

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:

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:

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:

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():

// 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():

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:

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
Coding Beauty Assistant logo

Try Coding Beauty AI Assistant for VS Code

Meet the new intelligent assistant: tailored to optimize your work efficiency with lightning-fast code completions, intuitive AI chat + web search, reliable human expert help, and more.

See also