回顾:
作用域和词法作用域:作用域就是查找变量(去哪儿找,怎么找)的一套规则。词法作用域在你写代码的时候就确定了。通过变量定义的位置就能知道变量的作用域。
变量作用域分为:全局变量和局部变量。JavaScript可以在函数内部直接读取全局变量。
var n=999;
function f1(){
alert(n);
}
f1(); // 999
而在函数外部自然无法读取函数内的局部变量。
function f1(){
var n=999;
}
alert(n); // error
注意:函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
function f1(){
n=999;
}
f1();
alert(n); // 999
那么该如何从外部读取局部变量呢?
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
函数f2
就被包括在函数f1
内部,这时f1
内部的所有局部变量,对f2
都是可见的。但是反过来就不行,f2
内部的局部变量,对f1
就是不可见的。即f2
函数为闭包。
既然f2
可以读取f1
中的局部变量,那么只要把f2
作为返回值,我们就可以在f1
外部读取它的内部变量了!!!如下:
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
父对象的所有变量,对子对象都是可见的,反过来不行。
闭包就是能够读取其他函数内部变量的函数,可以简单理解成定义在一个函数内部的函数。闭包是将函数内部和函数外部连接起来的桥梁。
闭包的用途
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
result
实际上就是闭包f2
函数,第一次运行后,函数f1
的局部变量一直保存在内存中,没有在调用f1
后自动清除。因此第二次调用时在其基础加一。
即:
- 可以从外部读取函数内部的变量
- 这些变量的值始终保持在内存中,不会在
f1
调用后被自动清除。
循环与闭包
for(var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
上面的这段代码,预期是每隔一秒,分别输出 0, 1, 2, 3, 4
, 但实际上依次输出的是 5, 5, 5, 5, 5
。
延迟函数的回调会在循环结束时才执行。这个循环的终止条件是 i 不再 < 5
,条件首次成立时i
的值是5
,因此,输出显示的是循环结束时i
的最终值。
尽管循环中的五个函数都是在各自迭代中分别定义的,但是他们都封闭在一个共享的全局作用域中,因此实际上只有一个i
。即所有函数共享一个i
的引用。
修改如下:
for(var i = 0; i < 5; i++) {
(function(j){
setTimeout(() => {
console.log(j);
}, j * 1000);
})(i)
}
在每次迭代内会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代内部都会含有一个具有正确值的变量可以访问。
总结
- 闭包是指有权访问另一个函数作用域中变量的函数。
- 闭包通常用来创建内部变量,使得 这些变量不能被外部随意修改,同时又可以通过指定的接口来操作。