DEV Community

Sean Ryan
Sean Ryan

Posted on • Edited on

A quick and dirty guide to the call, apply, and bind methods

High-level summary for the bosses: call, apply and bind are methods we can call on functions to make us less subject to the cruel whims of the this keyword.


If you're in the UK and you tell someone to take the lift to the second floor, they'll know you're talking about using the machine that transports people between different floors of a building. In the US, someone hearing the same instruction might initially think someone is about to give them a piggyback ride up a flight of stairs.

The same is true when writing code — reserved keywords have different meanings depending on the scope, or execution context, in which they're used. In JavaScript, one of the trickiest examples of a keyword that changes its meaning at the drop of a hat is this.

What is this?
The this keyword is a pointer that refers to a scope, or execution context, in your program. Which scope it refers to depends on where it is used. When used outside of a declared object, this points to the global object. (In the browser, the global object is actually the window object. If you open up the console in the inspector and type console.log(this), you will see the window object and all of its properties and methods logged).

Used inside of a declared object, this refers to the closest parent object. For example:

    const jim = {
      name: "Jim",
      age: 24,
      printAge: function() {
        return this.age
      }
    }

    const bob = {
      name: "Bob",
      age: 35,
      printAge: function() {
         return this.age
      }
    }

    jim.printAge() // returns 24
    bob.printAge() // returns 35

Like the rest of us, Jim is getting older. Let's add another method to the jim object that will take the number of years he has aged, and return his new age:

const jim = {
  name: "Jim",
  age: 24,
  printAge: function() {
    return this.age
  },
  increaseAge: function increaseAge(years){
        return this.name + " is now " + (this.age + years) + " years old."
    }
}

const bob = {
  name: "Bob",
  age: 35,
  printAge: function() {
     return this.age
  }
}

When we call jim.increaseAge(5), it will return Jim is now 29 years old. But what if we want to use this same method on the bob object? We could write it out again inside of bob, but that would be redundant. Instead, we'll use some trickery to redefine this when we call increaseAge so that it refers to the bob object, and the name and age properties therein.

call to the rescue
call is a method that we can use to specify what the keyword this refers to in the function to which we're adding it. The first argument that we pass into call is known as the thisArg. This is the object we want this in the function to refer to. The subsequent arguments are simply the regular arguments that we want to pass into the function.

Using call, we can access the properties and methods of the bob object when we invoke jim.increaseAge:

const jim = {
  name: "Jim",
  age: 24,
  printAge: function() {
    return this.age
  },
  increaseAge: function increaseAge(years){
        return this.name + " is now " + (this.age + years) + " years old."
    }
}

const bob = {
  name: "Bob",
  age: 35,
  printAge: function() {
     return this.age
  }
}

jim.increaseAge.call(bob, 5) // returns "Bob is now 40 years old."

apply allows us to control what this refers to inside of a function when we call it from outside of the context in which that function was defined. This is the sort of sorcery that allows us to write more versatile and reusable code.

apply is call's slightly higher achieving older sibling
Like call, apply specifies the context that this in the function will take in its first argument (the thisArg). call can only pass a predetermined number of arguments to a function, however, whereas apply passes an array of arguments to a function that are then unpacked and passed as parameters to the function.

Let's take a look at how this works:

const obj1 = {
    num: 5
}

const obj2 = {
    num: 2
}

const addToObj = function(a, b, c) {
    return this.num + a + b + c
}

let arr1 = [2, 3, 5, 6]
let arr2 = [4, 6, 3, 9]

console.log(addToObj.apply(obj1, arr1)) // logs 15 to the console

console.log(addToObj.apply(obj1, arr2)) // logs 18 to the console

console.log(addToObj.apply(obj2, arr1)) // logs 12 to the console

console.log(addToObj.apply(obj2, arr2)) // logs 15 to the console

Above, we declare arrays as variables then use these variables as apply's second argument to pass them into the function as separate parameters. With call, we would not be able to do this, as call requires the functions arguments to be passed as regular comma-separated parameters.

The bind method: delayed gratification
bind is the third in this family of methods that allow us to redefine the context of this when we call functions. While the difference between call and apply is subtle, the difference between bind and call and apply is more significant.

Rather than immediately invoking the function on which it's called, bind returns a function definition with the keyword this set to the value of the first argument passed into it (the thisArg). This is very useful when we know we want to redefine this in a function, but we don't know which arguments we want to pass into the function.

Let's go back to bob and jim to take a look at how bind works:

var bob = {
   firstName: "Bob",
   sayHi: function(){
      return "Hi " + this.firstName
   },
   addNumbers: function(a,b,c,d){
      return this.firstName + " just calculated " + (a+b+c+d)
   }
}

var jim = {
   firstName: "Jim"
}

var jimAdd = bob.addNumbers.bind(jim, 1, 2, 3, 4)

jimAdd() // Jimm just calculated 10

Here, we took the method addNumbers on the bob object and called the bind method on it to create another function in which this is redefined as the jim object. We stored this new function in the variable jimAdd, then subsequently called jimAdd. This works, because unlike call and apply, bind doesn't immediately invoke the function it's used on.

Don't know your arguments yet? Get out of your bind with bind.
When we use bind, we do not always need to know the parameters to the function we are creating — the only argument we need to pass into bind is the thisArg.

Let's return once again to Jim and Bob:

var bob = {
   firstName: "Bob",
   sayHi: function(){
      return "Hi " + this.firstName
   },
   addNumbers: function(a,b,c,d){
      return this.firstName + " just calculated " + (a+b+c+d)
   }
}

var jim = {
   firstName: "Jim"
}

var jimAdd2 = bob.addNumbers.bind(jim, 1,2)
jimAdd2(3,7) // returns 'Jim just added 13'

When we create the jimAdd2 function with bind, we only pass two arguments into the function. When we call it on the following line, we pass two additional arguments, and the function is called with all four arguments.

Using bind with asynchronous code:
Another common application of the bind method is is asynchronous code.

var bob = {
   firstName: "Bob",
   sayHi: function(){
      setTimeout(function(){
        console.log("Hi " + this.firstName)
      },1000)
   }
}

bob.sayHi() // Hi undefined (1000 miliseconds later)

Since the closest parent object to the sayHi method is bob, it would make sense that sayHi returns Hi Bob. However, because setTimeout is called at a later point in time, the object that it is attached to at the point of execution is not the bob object, but the window object.

If we still want the context of this in the setTimeout function to be the bob object when it is called, bind provides a perfect solution. It allows us to explicitly set the context of this without immediately invoking the function:

var bob = {
   firstName: "Bob",
   sayHi: function(){
      setTimeout(function(){
         console.log("Hi " + this.firstName)
     }.bind(this),1000)
   }
}

bob.sayHi() // "Hi Bob"

In this example, we can actually pass this in as the thisArg of bind on the setTimeout function, because in this case, this is equal to the bob object itself. (We could just as easily have passed in bob, but often, we will see code that uses this in cases such as this instead of the name of the object.

Top comments (0)