JS闭包学习笔记(2):循环和闭包

经典例子:

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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值