JS闭包学习笔记(2):循环和闭包
16lz
2021-01-22
经典例子:
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);
}
更多相关文章
- 带有无线电的JavaScript条件字段不起作用
- 即使在向上滚动时,AngularJS无限滚动调用也会起作用
- jQuery:执行一个函数AFTER toggleClass被执行
- JS在页面加载时候onload与匿名自调用函数的区别
- 在jQuery的$.post中调用函数时,Undefined不是对象
- 使用Sinon模拟require()函数
- 如何使函数等到对象的值未定义为js setTimeout
- 放在和中的javascript语句,但是语句不在函数中,这些语句何在被执行
- [JavaScript]自执行函数