你不知道的JS系列 ( 15 ) - 循环和闭包

要说明闭包,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)
}
很酷,块作用域和闭包联手便可天下无敌
 
原文地址:https://www.cnblogs.com/wzndkj/p/12364998.html