作用域闭包
function foo() {
var a = 2;
function bar () { //bar()的词法作用域能够访问foo()的内部作用域
console.log(a);
}
return bar; //然后将bar()函数本身当做一个值进行传递
}
var baz = foo(); //foo()执行后,其返回值(即内部的bar()函数)赋值给变量baz
baz(); //2 —— 闭包的效果
通常来说,foo()函数执行后,其整个内部作用域都会被销毁(垃圾回收机制),而闭包的“神奇”之处正是可以阻止这件事发生。
bar()依然持有对foo()内部作用域的引用,这个引用就叫做闭包。
闭包使得函数可以继续访问定义时的词法作用域。
循环和闭包
预期希望分别输出数字1-5,每秒一次,每次一个;
但实际上,代码运行时会已每秒一次的频率输出5次6(6产生的原因:循环的终止条件是i不再<= 5,条件首次成立时的值是6.故输出显示的是循环结束时i的最终值。)。
for (var i = 1; i <= 5; i++) {
setTimeout(function timer(){
console.log(i);
},i * 1000);
};
根据作用域的工作原理,实际情况为:尽管循环中的五个函数是在各个迭代中分别定义的,但他们都被封闭在一个共享的全局作用域中,因此实际上只有一个i。
故我们需要更多的闭包作用域,特别是在循环过程中每个迭代都需要一个闭包作用域。
方法一:用IIFE产生闭包解决
IIFE(立即执行函数)会通过声明并立即执行一个函数来创建作用域。但若作用域为空,仅将其封闭是不够的,故IIFE需要有自己的变量,用来在每个迭代中储存i的值:
for (var i = 1; i <= 5; i++) {
(function(){
var j = i; //IIFE需要有自己的变量,用来在每个迭代中储存i的值
setTimeout (function timer (
console.log(j);
),j * 1000);
})();
}
将这段代码进行改进之后:
for (var i = 1; i <= 5; i++) {
(function(j){ //IIFE需要有自己的变量,用来在每个迭代中储存i的值
setTimeout (function timer ( //在迭代内使用IIFE会为每个迭代都生成一个新的作用域
console.log(j); //使得setTimeout函数的回调可以将新的作用域封闭在每个迭代的内部
),j * 1000);
})(i);
}
方法二:let
for (var i = 1; i <= 5; i++) {
let j = i; // 用let声明可以劫持块作用域,并在块作用域中声明一个变量
setTimeout(function timer(){
console.log(j);
},j * 1000);
}
或者直接在for循环头部用let声明:
for (let i = 1; i <= 5; i++) {
setTimeout(function timer(){
console.log(i);
},i * 1000);
}