Wednesday, July 22, 2015

JavaScript: call(), apply(), & bind()

Introduction

JavaScript functions are actually objects themselves, and, like any other objects, contain properties and methods. Three of these methods, call(), apply(), and bind(), can be used to provide a calling context for a specific function, thereby treating ordinary functions like methods. This post aims to briefly describe and demonstrate how to use these methods (albeit with a contrived example).

Initial Setup

Our initial setup code is pretty straightforward: we have an object called person that has two properties (firstName and lastName) and one method (getName()). Running the code will log the person's full name to the console.

var person = {  
    firstName: "John",
    lastName: "Doe",
    getName: function() { 
        return this.firstName + " " + this.lastName; 
    }
};
console.log(person.getName()); //John Doe

The call() Method

At some point later in our application, we define a function to set the firstName and lastName properties at the same time.

var setName = function(newFirst, newLast) { 
    this.firstName = newFirst; 
    this.lastName = newLast;
};

The problem here is that we've declared this function outside of our person object. In this context, this refers to the global object (i.e. the Window object if our code is running in a web browser). So neither invoking the method directly on person (i.e. person.setName("Joe", "Schmoe")) or invoking it without indicating an object (i.e. setName("Joe", "Schmoe")) will do what we want it to do. How can we invoke this function as if it were a method on the person object? Our answer lies in the call() method.

The call() method allows you to invoke a function in the context of a specific object, which you provide as the first argument. In other words, the call() method lets you treat any function like a method on an object of your choosing. Any additional arguments provided are passed to the function being called (so invoking a function with n arguments via call() requires n+1 arguments).

setName.call(person, "Joe", "Schmoe");
console.log(person.getName());  //Joe Schmoe

In the above example, the setName() function is being called as if it were a method on the person object. So the code works the same way as if we had just given person a setName() method in the first place and were now invoking it like person.setName("Joe", "Schmoe").

The function being called doesn't have to be declare outside of an object; it can even be a method declared inside a different object. The example below utilizes the setFullName() method defined in customer to perform the same operation on person.

var customer = {
    ...
    setFullName: function(newFirst, newLast) {
        this.firstName = newFirst; 
        this.lastName = newLast;
    }
};
customer.setFullName.call(person, "Joe", "Schmoe");
console.log(person.getName());

The apply() Method

The apply() method functions almost identically to call(). The only real difference is that, while call() accepts a variable number of arguments, apply() only accepts two: the context object (i.e. the value of this inside the function) and an array of arguments to be passed in to the target function. So the equivalent code to our call() example utilizing apply() is written as follows:

setName.apply(person, ["Joe", "Schmoe"]);
console.log(person.getName()); //Joe Schmoe

The apply() method is obviously a bit more robust than call(), and can be used in scenarios where the number of function arguments is not known until runtime. Remembering which one is which can be a little confusing at first; I find it easiest to just remember the mnemonic "A for arrays, C for commas".

The bind() Method

The last method we will discuss is the bind() method. Much like the other methods we've discussed, the first argument supplied to bind() is the context object (i.e. the value of this inside the function). Unlike those other methods, bind() does not invoke a function when called. Instead, it returns a new function. Let's demonstrate this using another example:

var setPersonName = setName.bind(person);
setPersonName("Suzy", "Schmoe"); //Suzy Schmoe
console.log(person.getName()); 

The example above takes setName(), binds it to the person object, and stores it in a new function called setPersonName(). When this new function is invoked, it will modify the person object's properties. Notice that setPersonName() behaves like a method, but is not invoked like one. Bind is useful for creating shortcut functions (as in the above example) or in situations where you want to pass methods as anonymous function arguments to other functions.

Completed Example Code

var person = {  
    firstName: "John",
    lastName: "Doe",
    getName: function() { 
        return this.firstName + " " + this.lastName; 
    }
};
console.log(person.getName()); //John Doe

var setName = function(newFirst, newLast) { 
    this.firstName = newFirst; 
    this.lastName = newLast;
};

setName.call(person, "Joe", "Schmoe");
console.log(person.getName());  //Joe Schmoe

var customer = {
    firstName: "Sam",
    lastName: "Smith",
    setFullName: function(newFirst, newLast) {
        this.firstName = newFirst; 
        this.lastName = newLast;
    }
};

customer.setFullName.call(person, "Joe", "Schmoe");
console.log(person.getName());  //Joe Schmoe

setName.apply(person, ["Joe", "Schmoe"]);
console.log(person.getName()); //Joe Schmoe

var setPersonName = setName.bind(person);
setPersonName("Suzy", "Schmoe"); //Suzy Schmoe
console.log(person.getName());

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.