要说明闭包,for 循环是常见的例子
for (var i=1; i<=5; i++) { setTimeout(function timer() { console.log(i); }, 0) }
延迟函数的回调会在循环结束时才执行,执行循环的时候,变量的值已经变成 6 了,因此会每次输出一个 6 来。我们认为循环中的每个迭代在运行时都会给自己捕获一个 i 的副本。但是根据作用域的工作原理,调用的都是同一个 i。它只有一个 i。如果将延迟函数的回调重复定义 5 次,完全不使用循环,同这段代码是等价的
var i = 1; setTimeout(function timer() { console.log(i); }, 0) i = 2; setTimeout(function timer() { console.log(i); }, 0) i = 3; setTimeout(function timer() { console.log(i); }, 0) i = 4; setTimeout(function timer() { console.log(i); }, 0) i = 5; setTimeout(function timer() { console.log(i); }, 0) i = 6;
输出了 5 次 6。我们需要更多的闭包作用域,保留每次对 i 的引用
for (var i=1; i<=5; i++) { (function(){ setTimeout(function timer() { console.log(i); }, 0) })() }
这里增加了一层作用域,但是这个作用域里面是空的,它需要自己的变量,用来存储 i 的值
for (var i=1; i<=5; i++) { (function(){ var j = i;
setTimeout(function timer() { console.log(j); }, 0) })() }
行了,它能正常的工作了。可以对这段代码进行一些改进:
for (var i=1; i<=5; i++) { (function(j){ setTimeout(function timer() { console.log(j); }, 0) })(i) }
为每个迭代都生成一个新的作用域,使得延迟函数的回调都持有内部变量的访问。换句话说,每次迭代我们都需要一个块作用域,let 可以用来劫持块作用域。本质上是将块作用域转换成一个可以被关闭的作用域
for (let i=1; i<=5; i++) { let j = i; setTimeout(function timer() { console.log(j); }, 0) }
这样跟多封闭一层作用域的效果是一样的。但是 for 循环头部的 let 声明还会有一个特殊的行为,这个行为让变量在循环中不止被声明依次,每次迭代都会声明。
for (let i=1; i<=5; i++) { setTimeout(function timer() { console.log(i); }, 0) }
很酷,块作用域和闭包联手便可天下无敌