Bind

The this keyword plays a vital role in JavaScript. In JavaScript, this is based on how a function was called and not where it was declared (arrow functions behave the other way around).

Let’s take an example to demonstrate the this keyword.

const sayGreeting = {
    name: "Parwinder",
    hello: function() {
        return `Hello, ${this.name}`;
    }
}

console.log(sayGreeting.hello()); // Hello, Parwinder

The hello method can access the name property of the object sayGreeting. When I ran the method, it is prefixed by sayGreeting. and hence it runs in the context of sayGreeting object.

Instead if I did this:

const sayGreeting = {
    name: "Parwinder",
    hello: function() {
        return `Hello, ${this.name}`;
    }
}

const hello = sayGreeting.hello;

console.log(hello === sayGreeting.hello); // true
console.log(hello()); // Hello, undefined

Even though the variable hello is equal to the method on sayGreeting, the variable is not executed in the context of sayGreeting. It is executed in the window or global context.

bind allows us to bind context. It creates a new function where the this keyword is set to what we pass to bind method.

To make the above example, I can use the bind method to bind the context of sayGreeting to hello variable.

const sayGreeting = {
    name: "Parwinder",
    hello: function() {
        return `Hello, ${this.name}`;
    }
}

const hello = sayGreeting.hello.bind(sayGreeting);
console.log(hello()); // Hello, Parwinder

Where do we need to bind in real life?

In all the above example, the data being accessed and the function trying to access it is in the same object. There are times when you want to borrow method from an object but run it in the context of another.

const sayGreeting = {
    name: "Parwinder",
    hello: function () {
        return `Hello, ${this.name}`;
    }
}

const nameObject = {
    name: "Lauren"
}

const hello = sayGreeting.hello.bind(nameObject);

console.log(hello()); // Hello, Lauren

I have the hello method in sayGreeting object. There is no need to recreate it in nameObject. I can borrow the hello method and run it in the context of nameObject.

Call

call() and apply() differs from bind(). bind() returns a new function whereas call() and apply() invoke the existing function immediately. call() takes this as the first argument and then it allows you to pass arguments one by one. These arguments would be passed to the function we called.

const sayGreeting = {
    name: "Parwinder",
    hello: function () {
        return `Hello, ${this.name}`;
    }
}

console.log(sayGreeting.hello.call(sayGreeting)); // Hello, Parwinder

With arguments:

const sayGreeting = {
    name: "Parwinder",
    hello: function (trait, color) {
        return `Hello, ${this.name}. I see you ${trait} ${color}. It is my favorite too!`;
    }
}

console.log(sayGreeting.hello.call(sayGreeting, "like", "red"));
// Hello, Parwinder. I see you like red. It is my favorite too!

Apply

apply() even though executes the function immediately like call() does but it takes an array of arguments as a second parameter instead of comma-separated values.

const sayGreeting = {
    name: "Parwinder",
    hello: function () {
        return `Hello, ${this.name}`;
    }
}

console.log(sayGreeting.hello.apply(sayGreeting)); // Hello, Parwinder

No difference between apply and call when done without arguments. But, when used with arguments.

const sayGreeting = {
    name: "Parwinder",
    hello: function (trait, color) {
        return `Hello, ${this.name}. I see you ${trait} ${color}. It is my favorite too!`;
    }
}

console.log(sayGreeting.hello.apply(sayGreeting, ["like", "red"]));
// Hello, Parwinder. I see you like red. It is my favorite too!

apply makes it easier to send n number of arguments in an array. Sending multiple arguments is easier now in ES6 by using the rest (…) operator.