闭包可以简单理解为本作用域对其他作用域变量的引用,看如下代码:
for (var i = 1; i <= 5; i++) {
(function () {
setTimeout(function timer() {
console.log(i);
},i*1000);
})();
}
上述代码想每秒打印1~5这5个数字,但是最终的结果会打印5次“6”这个数字。
首先(function () {})();这个IIFE(立即执行函数)在for循环每次迭代的时候都会创建一个作用域,但是这个作用域是空的,在IIFE的作用域中打印的i变量仍然是for中声明定义的i,这就是闭包。据实践证明,setTimeout这个工具函数,是在for循环执行完毕之后才执行的,所以for循环的结束标志是i=6,此时i的取值为6。看如下修改:
for (var i = 1; i <= 5; i++) {
(function () {
var j = i;
setTimeout(function timer() {
console.log(j);
},j*1000);
})();
}
此时打印的就是1、2、3、4、5。
因为在IIFE内部的作用域声明了一个j变量,这个变量属于当前作用域,在for每次迭代的时候,函数会将i的值赋值给j,由j来保存,这样每次输出的值就是作用域中保存的j的值。 可以对代码改进:
for (var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(function timer() {
console.log(j);
},j*1000);
})(i);
}
当然,这种传统的写法或许已经过时了,块作用域的出现似乎可以代替IIFE这种写法,写法如下:
for (var i = 1; i <= 5; i++) {
let j = i;//闭包的块作用域
setTimeout(function timer() {
console.log(j);
},j*1000);
}
let声明的j变量就在当前创建了块作用域(也就是{}包含的内容),也即for循环每次迭代会创建块作用域,这个块作用域中有闭包,timer打印的也就是每个块作用域中的j变量的值。
当然,还有更高级的写法:
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
},i*1000);
}
for循环头部的let声明指出,for循环在let声明过程中不止会声明一次,每一次迭代都会声明。随后每一次迭代都会使用上一次迭代结束时的值来初始化这个变量。