JavaScript Learning Notes on Closures
Scope-related definitions
Before we talk about closures, let’s first talk about scopes. JavaScript has global scoping, function scoping. Look at the following code
1
2
3
4
5
6
var a = 10;//全局作用域中定义变量
(function(){
var b = 20;//函数作用域中定义变量
})();
console.log(a);//可以访问全局变量
console.log(b);//error, b is not defined
JavaScript does not have block-level scoping. Look at the following code
1
2
3
4
for(var item in {a:1,b:2}){
console.log(item);
}
console.log(item); //没有块级作用域,可以访问item
Incidentally, ES6 introduced block-level scoping and new variable declarations (let). JS uses var to declare variables, and function to delimit scope, but curly braces “{}” don’t delimit the scope of var. Variables declared with var have the effect of declaration hoisting, i.e., try it first and then declare it without error. ES6 adds a let, which can be declared in {}, if, and for. The usage is the same as var, but the scope is limited to the block level, and there is no variable hoisting for variables declared by let.
What is a closure
Here’s what Wikipedia says:
In computer science, a closure (also known as a lexical closure or function closure) is a function, or reference to a function, bound to a referential environment. This referential environment is a table that stores every non-local variable (also called free variable) of that function. A closure, unlike a normal function, allows a function to be called outside the immediate lexical scope and still have access to non-local variables.
JavaScript has closures because it is a language of the first type of functional properties, i.e., it can be used to pass a function as an object as a return value. Look at the following function, for which the local variable localVal can be released after the function outer() is called.
1
2
3
4
5
function outer(){
var localVal = 30;
return localVal;
}
outer();//30
In JavaScript, functions are also objects, and functions can also be used as return values or passed as parameters, and functions can also be applied to other functions. For the following function, calling function outer() returns an anonymous function, which still has access to the external local variable localVal. calling func() after calling outer() still has access to the external function’s local variable localVal, and localVal is not released. released. This is closure.
1
2
3
4
5
6
7
8
function outer(){
var localVal = 30;
return function(){
return localVal;
}
}
var func = outer();
func();//30
Closures are everywhere
As in the following function, the local variables of the external function can still be accessed in the click event
1
2
3
4
5
6
!function(){
var localData = "localData here";
document.addEvenetListener("click",function(){
console.log(localData);
});
}
and then the following asynchronous request, jquery’s $.ajax method, after the entire function call ends, the callback function can still access the url, localData these local variables
1
2
3
4
5
6
7
8
9
10
!function(){
var localData = "localData here";
var url = "https://www.qq.com/";
$.ajax({
url: url,
success: function(){
console.log(localData);
}
})
}
Pitfalls of closures
Look at the function below, what is the output?
1
2
3
4
5
6
7
8
document.body.innerHTML = "<div id='div1'>aaa</div>" + "<div id='div2'>bbb</div>" + "<div id='div3'>ccc</div>";
for(var i = 0; i < 4; i++){
document.getElementById('div' + i)
.addEventListener("click", function(){
alert(i);//all are 4!
}
);
}
In fact, clicking on any div outputs a result of 4. The callback function is executed when a div is clicked, and it’s only at this point that the function gets the value of i dynamically. This is all after the initialization of the whole process, after which the value of i is already 4. So how can we achieve the desired effect? Look at the following code
1
2
3
4
5
6
7
8
9
10
11
document.body.innerHTML = "<div id='div1'>aaa</div>" +
"<div id='div2'>bbb</div>" + "<div id='div3'>ccc</div>";
for(var i = 0; i < 4; i++){
!function(i){
document.getElementById('div' + i)
.addEventListener("click", function(){
alert(i);//1,2,3
}
);
}(i);
}
Here at each level of the loop, an immediately executable anonymous function is used to wrap the click event function and pass the parameter i, i.e., 1,2,3. Then each time the div is clicked, the i inside alert(i) takes the i from each closure environment, which comes from the i from each loop, so that clicking on each div pops up the corresponding value.
What closures do
Closures can be used to encapsulate some variables, look at the following code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(function){
var _userId = 23492;
var _typeId = 'item';
var export = {};
function converter(userId){
return +userId;
}
export.getUserId = function(){
return converter(_userId);
}
export.getTypeId = function(){
return converter(_typeId);
}
window.export = export;
}
export.getUserId(); //23492
export.getTypeId(); //item
export._UserId; //undefined
export._TypeId; //undefined
export.converter; //undefined
Here some local variables such as _userId are defined that cannot be accessed directly from the outside, and the object export is exported through window.export = export. When you use export externally, you can only access the local variables of the function through some of the methods of export, but you can’t access these variables and methods directly. This takes advantage of the nature of closures, such as the export.getUserId() function, which still has access to the local variable _userId after the entire anonymous function has been initialized. At the same time, closures can also bring some problems, such as local variables are not released resulting in wasted space; memory leaks; performance consumption and so on.
Reference
Links: front-end key knowledge organization (JavaScript) three: closure