As discussed in the article about the this keyword, execution context relates to this and can be directly changed using call, apply, and bind. This article will clarify these three methods, their differences, and their practical uses.
Basically, call and apply are quite similar and were introduced in ES3 according to the ECMAScript standard, while bind — introduced in ES5 — is fundamentally different yet closely related to the other two. So in this article, we’ll go from call and apply to bind.
Call and Apply
Syntax:
-
call()
Function.prototype.call(thisArg[, arg1[, arg2, …]])
-
apply()
Function.prototype.apply(thisArg, argArray)
The call() and apply() methods invoke a function with a specified context via the thisArg parameter and corresponding input parameters. That is, they allow a function to execute with an arbitrary specified context. The difference between them is that call() accepts function arguments as separate parameters, while apply() accepts them as an array. Let’s look at an example:
var obj = {
firstName: 'Vô',
lastName: 'Danh',
mMethod: function (firstName, lastName) {
var firstName = firstName || this.firstName;
var lastName = lastName || this.lastName;
console.log('Hello ' + firstName + ' ' + lastName);
},
};
var obj1 = {
firstName: 'Ông',
lastName: 'Ké',
};
obj.mMethod(); // Hello Vô Danh
obj.mMethod.call(obj1); // Hello Ông Ké
obj.mMethod.apply(obj1); // Hello Ông Ké
obj.mMethod.call(obj1, 'Thị', 'Nở'); // Hello Thị Nở
obj.mMethod.apply(obj1, ['Chí', 'Phèo']); // Hello Chí Phèo
From the code above, we can see that after calling call() or apply(), the execution context of mMethod has been switched to obj1. call() lets us pass input parameters individually, while apply() lets us pass them as an array.
Since ES5, apply() can also accept an array-like object instead of an array:
obj.mMethod.apply(obj1, ['Chí', 'Phèo']); // Hello Chí Phèo
obj.mMethod.apply(obj1, { length: 2, 0: 'Chí', 1: 'Phèo' }); // Hello Chí Phèo
Using call() or apply(), we can do many clever things like borrowing a method from another context, pushing execution context to a callback, or flexibly changing how parameters are passed:
- Passing execution context to a callback
function print() {
console.log(this.mVal);
}
var obj = {
mVal: 'I love JavaScript',
mMethod: function (callback) {
// pass the current object to the callback
callback.call(this);
},
};
obj.mMethod(print);
- Changing how function parameters are passed
// Math.min([value1[,value2[, ...]]])
// Use an array for input instead of discrete values
console.log(Math.min.apply(null, [100, -1, 8, 219])); // -1
Not bad, right? With call and apply, we gain flexibility in programming, saving effort on cumbersome transformations and reusing code effectively.
Bind
Syntax:
Function.prototype.bind(thisArg[, arg1[, arg2, …]])
The bind() method creates a new function whose body is the same as the called function but bound to a specified execution context via the thisArg parameter. Unlike call() and apply(), bind() does not immediately execute the function — it stores the execution context for later use:
var obj = {
mVal: 'Vietnam',
mMethod: function () {
console.log('Hello ' + this.mVal);
},
};
var oMethod = obj.mMethod.bind(obj); // this in oMethod is forced to be obj
oMethod(); // Hello Vietnam
obj.mVal = 'Hanoi';
oMethod(); // Hello Hanoi
Unlike call() and apply(), calling bind() doesn’t execute the function immediately — it only binds the execution context for mMethod. Because mMethod is bound to the obj context, every execution uses this as the obj object. As shown above, after changing mVal in obj, mMethod prints the updated value.
Essentially, bind() can be implemented as follows:
Function.prototype.bind = function (context) {
var _this = this;
return function () {
_this.apply(context, arguments);
};
};
By using apply(), we can change the execution context for a function, and to persist that context for later execution, we create a new function as shown above.
Because of its context-persisting nature, bind() is commonly used with callbacks such as click event handlers. For example, when using jQuery, this in click event callbacks is automatically bound to the button element.
<!doctype html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script>
$(document).ready(function () {
$('#btn').click(function () {
console.log(this);
$(this).text(Number($(this).text()) + 1);
});
});
</script>
</head>
<body>
<button id="btn">0</button>
</body>
</html>
Additionally, we can do many interesting things like creating shortcuts for functions or grouping parameters:
var obj = {
firstName: 'Thánh',
lastName: 'Gióng',
mMethod: function (hello, firstName, lastName) {
var hello = hello || 'Hello',
firstName = firstName || this.firstName,
lastName = lastName || this.lastName;
console.log(hello + ' ' + firstName + ' ' + lastName);
},
};
// create shortcut
var print = obj.mMethod.bind(obj);
print();
var print = obj.mMethod.bind(obj, 'Hello', 'Mr', 'Bean');
print();
// Group by greeting
print = obj.mMethod.bind(obj, 'Xin chào bạn');
print();
// Group by firstName
print = obj.mMethod.bind(obj, 'Kính chào ngài', 'Nguyễn');
print('Trãi');
print('Xiển');
A fun trick
As analyzed above, when we need to execute a function in a different context, we must use call, apply, or bind. But there’s a way to bypass them:
var obj = {
firstName: 'Vô',
lastName: 'Danh',
mMethod: function (firstName, lastName) {
var firstName = firstName || this.firstName;
var lastName = lastName || this.lastName;
console.log('Hello ' + firstName + ' ' + lastName);
},
};
var obj1 = {
firstName: 'Ông',
lastName: 'Ké',
};
obj.mMethod.apply(obj1); // Hello Ông Ké
// bypass here
var method = Function.call.bind(obj.mMethod);
method(obj1); // Hello Ông Ké
// bypass in object prototype
method = Function.call.bind(Array.prototype.slice);
console.log(method([100, 20, 40], 1)); // [20, 40]
With the code above, when calling method, we no longer need to call call, apply, or bind — we just directly pass the target object and parameters. Quite neat, right?
This works because all functions in JavaScript inherit from Function.prototype, so they naturally have all methods defined on Function.prototype like call, apply, or bind. Thus, we can call these inherited methods from any function.
In the example above, we treat obj.mMethod as the object to call call on, creating a method directly from call that has been bound with thisVal equal to obj.mMethod: Function.call.bind(obj.mMethod) or Function.prototype.call.bind(obj.mMethod). In other words, the new method has the same body as call and its owner (this) is obj.mMethod, so method() ~ obj.mMethod.call().
Conclusion
Using call, apply, and bind, we can change the execution context to use a function more flexibly — executing it for a different object or scope, maximizing code reuse, creating function shortcuts, and handling input parameters more flexibly. With call and apply, we execute the function immediately; with bind, we can execute it multiple times after it has been bound to a specific context.
Comments