This article is translated from MDN (Mozilla Developer Network):
- Original address: MDN
- Translation Address: shixinzhang's blog
After reading this article, you will learn:
Lexical Scope
Consider the following code:
function init() {
var name = 'Mozilla'; // name is a local variable created by the init function
function displayName() { // displayName() is the inner method of a function, a closure
alert(name); // It uses variables declared by parent functions
}
displayName();
}
init();
The init() function creates the local variable name and the function displayName().
displayName() is an internal function defined within init(), so it can only be accessed within init(). displayName() has no internal variables, but because internal functions can access variables of external functions, displayName() can access the variable name in init().
Running the above code, we can see that the value of name has been printed successfully.
This is an example of "lexical scope" (which describes how the JS parser handles variables in nested functions).
Lexical scope refers to the location of a variable declared in the source code as its scope. At the same time, nested functions can access variables declared in their outer scope.
closure
Now take a look at the following code:
function makeFunc() {
var name = 'Mozilla';
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
Running the above code will produce the same results as the first example. The difference is - and interestingly enough - that the internal function displayName() is returned as a return value by an external function before execution.
At first glance, this code, though executable, is not intuitive. In some programming languages, local variables within a function survive only during the execution of the function. Once the makeFunc() function is executed, you may feel that the name variable cannot exist. However, judging from the results of the code, JavaScript is obviously different from the "some programming languages" we mentioned earlier about variables.
The "difference" of the above code is that makeFunc() returns a closure.
Closure consists of a function and its lexical environment. This environment refers to all variables that a function can access when it is created. In the example above, myFunc refers to a closure consisting of the displayName() function and the "Mozilla" string that existed when the closure was created. Because displayName() holds a reference to name and myFunc holds a reference to displayName(), name is still accessible when myFunc calls.
Here is a more interesting example:
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
In the example above, makeAdder() accepts a parameter x and returns a function with a parameter Y and a return value of x+y.
Essentially, makeAdder() is a function factory -- passing in a parameter for it creates a function whose parameters sum with other values.
In the example above, we used the function factory to create two functions, one with a parameter of 5 and the other with 10.
add5 and add10 are closures. They use the same function definition, but the lexical environment is different. In add 5, x is 5; in add10, x is 10.
Callback of Closed Real Battlefield Scene
Closure is useful because it can associate some data with functions that operate on it. This is obviously similar to object-oriented programming. In object-oriented programming, we can associate some data (object attributes) with one or more methods.
Therefore, when you want to manipulate an object in only one way, you can use closures.
In web programming, there may be many scenarios where you use closures. Most front-end JavaScript code is event-driven: we define behavior and then associate it with a user event (click or press). Our code is usually bound to events as a callback (a function called when an event is triggered).
For example, we want to add a few buttons to a page to adjust the font size. One method is to specify font-size of body elements in pixels, and then set the font-size of other elements (such as headers) in the page by relative em units.
Here is the CSS code:
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}
The font-size attribute of the body element can be modified by the button that modifies the font-size of the font size, and because we use relative units, other elements in the page will be adjusted accordingly.
HTML code:
<p>Some paragraph text</p>
<h1>some heading 1 text</h1>
<h2>some heading 2 text</h2>
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
JavaScript code:
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
size12, size14, and size16 can now adjust body fonts to 12, 14, and 16 pixels, respectively. We can then bind them to the button:
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
Now click a few buttons separately, and the font of the whole page will be adjusted.
Simulating Private Methods with Closures
Some programming languages, such as Java, can create private methods (methods that can only be called by other methods in the same class).
JavaScript does not support this approach, but we can use closure emulation to implement it. Private methods not only restrict access to code, but also provide a powerful ability to manage global namespaces to avoid non-core methods messing up the public interface of code.
The following code shows how to use closures to define public functions that can access private functions and variables. This approach is also called modular mode:
var counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
};
})();
console.log(counter.value()); // logs 0
counter.increment();
counter.increment();
console.log(counter.value()); // logs 2
counter.decrement();
console.log(counter.value()); // logs 1
In the previous example, each closure has its own lexical context. But in this example, three methods counter.value(), counter.increment() and counter.decrement() share a lexical environment.
This shared environment is created within an anonymous function that executes as soon as it is defined. The environment contains two private items: a variable named privateCounter and a function named changeBy. Neither of them can be accessed directly outside anonymous functions. It must be accessed through three common functions of the object returned by the anonymous wrapper.
Thanks to the lexical scope of JavaScript, these three functions can access privateCounter and changeBy(), enabling their three closures to share an environment.
You may notice that in the above code, we created privateCounter in an anonymous function, then immediately executed the function, assigned a value to privateCounter, and returned the result to counter.
We can also save this function to another variable to create multiple counters.
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var counter1 = makeCounter();
var counter2 = makeCounter();
alert(counter1.value()); /* Alerts 0 */
counter1.increment();
counter1.increment();
alert(counter1.value()); /* Alerts 2 */
counter1.decrement();
alert(counter1.value()); /* Alerts 1 */
alert(counter2.value()); /* Alerts 0 */
Now two counters counter1 and counter2 have different lexical environments, and they have their own privateCounter and values. Calling one counter does not affect the other value.
Using closures in this way can provide many benefits in object-oriented programming, such as data hiding and encapsulation.
Common error: creating closures in loops
Before ECMAScrpit 2015, there was no let keyword.
Creating closures in loops is a common mistake. Take the following code for example:
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i]; //Variables declared by var, whose scope is in the body of a function, not in a block
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
setupHelp();
In the above code, helpText is an array of three IDs associated with the prompt information. In the loop, we traverse the helpText array, adding a response to the focused event to the component corresponding to the id in the array.
If you run the above code, you will find that no matter which input box you choose, the final prompt will be "Your age".
The reason is that we assign onfocus events to three closures. The three closures consist of the environment within the function and setUpHelp().
Three closures are created in the loop, but they all use the same lexical environment item, which has a variable item.help whose value changes.
When the onfocus callback executes, the value of item.help is determined. By that time, the loop was over, and the item object shared by the three closures pointed to the last item in the helpText list.
One solution to this problem is to use more closures, such as the function factory mentioned earlier:
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function makeHelpCallback(help) {
return function() {
showHelp(help);
};
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i];
document.getElementById(item.id).onfocus = makeHelpCallback(item.help); //Here we use a function factory
}
}
setupHelp();
In this way, the result is correct. Unlike the previous example, the three callbacks share a lexical environment. In the code above, a new lexical environment is created for each callback using the makeHelpCallback() function. In these environments, help points to the correct string in the helpText array.
Another way to use anonymous functions to solve this problem is to write as follows:
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
(function() {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
})(); // Call the binding function immediately and bind to the event with the correct value; instead of using the value at the end of the loop
}
}
setupHelp();
If you don't want to use more closures, you can also use the block-level scope let keyword introduced in ES2015:
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function setupHelp() {
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
];
for (var i = 0; i < helpText.length; i++) {
let item = helpText[i]; //Limit scope only within the current block
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
setupHelp();
The above code modifies the variable item with let rather than var, so each closure binds variables within the current block. No additional closures are required.
Attention performance
It is not advisable to create functions in other functions if they are not necessary. Because closures have a negative impact on script performance, including processing speed and memory consumption.
For example, when creating new objects or classes, methods should usually be associated with the prototype of the object, rather than defined in the object constructor. Because this will cause the method to be reassigned every time the constructor is called (that is, when each object is created, the method is reassigned).
For instance:
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
this.getName = function() {
return this.name;
};
this.getMessage = function() {
return this.message;
};
}
The code above does not take advantage of closures, so we can rewrite it as follows:
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype = {
getName: function() {
return this.name;
},
getMessage: function() {
return this.message;
}
};
Generally speaking, however, it is not recommended to redefine prototypes.
The following code adds attributes to an existing prototype:
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype.getName = function() {
return this.name;
};
MyObject.prototype.getMessage = function() {
return this.message;
};
However, we can also abbreviate the above code as follows:
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
(function() {
this.getName = function() {
return this.name;
};
this.getMessage = function() {
return this.message;
};
}).call(MyObject.prototype);
In the previous three examples, inherited prototypes can be shared by all objects without having to redefine methods every time an object is created.