Keywords: JavaScript | this keyword | scope binding | closures | code readability
Abstract: This article provides an in-depth examination of the common 'var that = this;' coding pattern in JavaScript, analyzing its core mechanism for solving dynamic scope issues with the 'this' keyword. Through practical examples involving event handling and nested functions, it explains how variable aliasing preserves original context references. The discussion also covers the impact of naming conventions on code readability and offers alternative semantic naming strategies. Incorporating Douglas Crockford's classic explanation, the article comprehensively explores this pattern's application value in closures and object-oriented programming.
The Dynamic Nature of the this Keyword in JavaScript
In JavaScript programming, the behavior of the this keyword often perplexes developers. Unlike many class-based languages, the value of this in JavaScript is not statically bound to the function where it is declared, but dynamically determined by how the function is invoked. This design means that within nested functions or callbacks, the reference of this may unexpectedly change, leading to logical errors in programs.
Scope Loss Problem and Solution
Consider this typical scenario: an event listener containing array iteration operations. When a user clicks a page element, this within the event handler correctly points to the clicked DOM element. However, inside the callback function of the forEach method, the context of this changes, typically becoming undefined (in strict mode) or the global object (in non-strict mode).
var colours = ['red', 'green', 'blue'];
document.getElementById('element').addEventListener('click', function() {
// this points to the clicked DOM element
var that = this;
colours.forEach(function() {
// this becomes undefined or global object
// that still points to the original clicked element
console.log(that.id); // Accessible normally
});
});
By declaring var that = this;, we save the current scope's this value into the local variable that. Due to JavaScript's lexical scoping rules, inner functions can access variables from outer functions, so that remains available within nested functions, thereby solving the context loss problem.
Similar Pattern in jQuery
In libraries like jQuery, this issue similarly exists but manifests slightly differently. jQuery's each method sets this within its callback to point to the current iteration element, which may conflict with the outer function's this.
$('#element').click(function(){
// this points to the clicked jQuery element
var that = this;
$('.elements').each(function(){
// this points to the current element in .each loop
// that remains the original clicked element
console.log($(that).attr('id'));
});
});
Readability Issues with Naming Conventions
Although that as an alias has become something of a community convention, this naming approach has significant readability drawbacks. In longer functions or complex nested structures, the specific meaning of that can become ambiguous, increasing code maintenance difficulty.
A better practice is to use semantically clear variable names. For example, in the aforementioned event handling scenario, using descriptive names like clickedElement or originalContext can significantly enhance code self-documentation:
document.getElementById('button').addEventListener('click', function() {
var clickedButton = this;
// The meaning of clickedButton is immediately clear in subsequent code
processData(function() {
enableButton(clickedButton);
});
});
Application in Object-Oriented Programming
Douglas Crockford, in his classic discussion on JavaScript private members, notes that the var that = this; pattern is an important workaround for addressing incorrect this binding in inner functions within the ECMAScript specification.
Consider this constructor example:
function User(name) {
var that = this; // Save current instance reference
this.name = name;
function displayName() {
// In inner function, this no longer points to User instance
// but that remains accessible
console.log(that.name);
}
this.showName = function() {
displayName();
};
}
var user = new User('Alice');
user.showName(); // Correctly outputs 'Alice'
Without using the that pattern, this within the displayName function would point to the global object (non-strict mode) or undefined (strict mode), preventing access to instance properties.
Modern JavaScript Alternatives
With the widespread adoption of ECMAScript 6, arrow functions provide a more elegant solution. Arrow functions do not bind their own this, but instead inherit the this value from the enclosing function context:
// ES6 arrow functions simplify context preservation
colours.forEach(() => {
// this in arrow function is same as outer function
console.log(this.id); // No need for that variable
});
Additionally, the Function.prototype.bind() method can be used to explicitly bind function context:
var handler = function() {
console.log(this.id);
}.bind(this);
colours.forEach(handler);
Conclusion
The var that = this; pattern is a crucial technique in JavaScript development for handling dynamic scope. Through variable aliasing, it preserves original context references within nested functions, solving the core problem of this binding loss. While effective, developers should prioritize code readability by considering semantic naming or modern language features like arrow functions. Understanding this pattern's principles helps in writing more robust, maintainable JavaScript code, particularly in scenarios involving event handling, asynchronous programming, and object-oriented design.