Excerpt from Closure: The Definitive Guide by Michael Bolin coming up next! It's a bit long, but it's packed with valuable insights. Here's a snippet from "Appendix B. Frequently Misunderstood JavaScript Concepts":
Mysterious Behavior of 'this' in Function Calls
When you call a function like foo.bar.baz()
, the object foo.bar
becomes the receiver. In this case, the value of this
inside the function will be the receiver:
var obj = {};
obj.value = 10;
/** @param {...number} additionalValues */
obj.addValues = function(additionalValues) {
for (var i = 0; i < arguments.length; i++) {
this.value += arguments[i];
}
return this.value;
};
// Outputs 30 because 'obj' is the context for 'this' when calling
// obj.addValues(), so obj.value now equals 10 + 20.
obj.addValues(20);
In cases where there's no explicit receiver during a function call, the global object steps in as the receiver. For instance, in a web browser environment, the global object is typically 'window'. This can lead to unexpected outcomes:
var f = obj.addValues;
// Results in NaN because 'window' is the context for 'this' in
// f() call. As window.value is undefined, adding a number leads to NaN.
f(20);
// Also adds a property to window unintentionally:
alert(window.value); // Displays NaN
Different behavior can occur even if two functions refer to the same method but have different receivers for 'this'. Therefore, it's crucial to ensure that 'this' references the correct value when executing a function. If 'this' isn't used within the function body, then both f(20)
and obj.addValues(20)
would produce similar results.
In JavaScript, functions are treated as objects and possess their own methods. Every function includes call()
and apply()
, allowing you to redefine the receiver ('this') during the function invocation. The methods look like this:
/**
* @param {*=} receiver to replace 'this'
* @param {...} parameters as function arguments
*/
Function.prototype.call;
/**
* @param {*=} receiver to replace 'this'
* @param {Array} parameters as function arguments
*/
Function.prototype.apply;
Notice that the key difference between call()
and apply()
is how they handle arguments—individually or as an array:
// Invoking 'f' with 'obj' as its receiver behaves similarly to calling
// obj.addValues(). Both result in increasing obj.value by 60:
f.call(obj, 10, 20, 30);
f.apply(obj, [10, 20, 30]);
The following calls achieve the same outcome since 'f' and 'obj.addValues' point to the identical function:
obj.addValues.call(obj, 10, 20, 30);
obj.addValues.apply(obj, [10, 20, 30]);
However, neither call()
nor apply()
uses its receiver value if not specified, causing issues like:
// Both expressions assess to NaN
obj.addValues.call(undefined, 10, 20, 30);
obj.addValues.apply(undefined, [10, 20, 30]);
'this' in a function call cannot be null or undefined. Passing them as receivers to call()
or apply()
prompts the global object as substitute. Consequently, the code above inadvertently appends 'value' to the global object.
Consider viewing a function as oblivious to the variable it's assigned to. This aids in recognizing that 'this' binds its value upon function execution rather than definition.
End of excerpt.