经典例子:

for(var i=1;i<=5;i++){
setTimeout(function timer(){
console.log(i);
},i*1000);
}

最终的结果并不如我们期待的打印出1,2,3,4,5,而是打印出6,6,6,6,6.

setTimeout的回调函数timer会在循环结束后才开始执行,因此会输出i最后的值6.即使将setTimeout的第二个参数设为0,timer还是会在循环结束后才执行,因此还是会打印6.

我们本来期待每一次循环中都能保留当时的i,但当闭包起作用时,虽然每个函数是在各次循环中依次定义的,但它们最后都share同一个作用域,即全局作用域,因此都使用同一个i。五个函数其实只是一个接一个地被声明,其中并没有循环存在了。

如何改进呢?
首先想到使用IIFE(立即执行函数):

for(var i=1;i<=5;i++){
(function(){
setTimeout(function timer(){
console.log(i);
},i*1000)
})();
}

IIFE的确增加了一层作用域,每次setTimeout执行都会闭包其当前循环中的IIFE作用域,此时的确每个timer函数闭包了自己的作用域,但IIFE是空的,最后导致每一个timer还是会调用同一个全局变量i,值为6.

从空的IIFE进行改进:每个函数都需要它自己的变量,因此要在在每次循环中保存当时的i:

for(var i=1;i<=5;i++){
(function(){
var j = i;
setTimeout(function timer(){
console.log(j);
},j*1000)
})();
}

这回才得出正确的结果!

另一种写法:

for(var i=1;i<=5;i++){
(function(j){
setTimeout(function timer(){
console.log(j);
},j*1000)
})(i);
}

每一次循环中的IIFE为当次循环创建了一个新的作用域,这样其内部的setTimeout回调函数可以闭包这个新作用域,这个作用域有保存了当时i的值的变量j,因此每一个回调函数可以使用这个j并输出正确结果。

在每次循环中IIFE创建了一个新的作用域,换句话说,我们需要一个基于每次循环的块作用域。let关键字可以拦截一个块,并在此块中声明一个变量,此块就可以变成闭包的作用域,因此下面的代码也可以正确解决问题:

for(var i=1;i<=5;i++){
let j=i; //为闭包创建了一个块作用域
setTimeout(function timer(){
console.log(j);
},j*1000);
}

甚至可以直接把let关键字作用于for循环的头部,此时let声明的变量不是在循环中只声明一次,而是在每次循环时都会被声明,而且被初始化为上次循环结束时的值:

for(let i=1;i<=5;i++){
setTimeout(function timer(){
console.log(i);
},i*1000);
}

更多相关文章

  1. 带有无线电的JavaScript条件字段不起作用
  2. 即使在向上滚动时,AngularJS无限滚动调用也会起作用
  3. jQuery:执行一个函数AFTER toggleClass被执行
  4. JS在页面加载时候onload与匿名自调用函数的区别
  5. 在jQuery的$.post中调用函数时,Undefined不是对象
  6. 使用Sinon模拟require()函数
  7. 如何使函数等到对象的值未定义为js setTimeout
  8. 放在和中的javascript语句,但是语句不在函数中,这些语句何在被执行
  9. [JavaScript]自执行函数

随机推荐

  1. 使用.php文件生成一个MySQL转储文件。
  2. 使用mod_rewrite将文件夹转换为查询字符
  3. PHP的钩子实现解析
  4. 准备好的报表没有速度效益吗?
  5. 上传文件时通过AJAX更新列表
  6. 现代 PHP 新特性 —— 闭包
  7. Mysql PHP生成的表:不能使用表
  8. PHP如何检查MySQLi查询是否需要关闭?
  9. 几个有用的php字符串过滤,转换函数代码
  10. 使用/发送POST数据时的libcurl C问题(不