理解:JS中的闭包closure

直接上代码例子

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

我们会在控制台看到期望的12345?如果你执行下就会发现并不会,而是会得到66666。

6是哪来的呢?为了回答这个问题,

首先,6是for循环终止后i的值,即便我们给setTimeout的第二个参数传递0,它的回调函数timer也同样会在for循环结束之后才执行;

其次,for循环在JS中是没有自己作用域的。因此,循环变量i实际上是定义在全局作用域的变量;

第三,每次循环,timer通过closure捕获的都是同一个全局变量i。

思路:不要让所有的timer都捕获同一个变量i,而是让它们捕获当前迭代时i的一个拷贝。

首先,让timer在每一次迭代的时候,捕获自己的作用域,而不是捕获全局作用域,这可以通过IIFE实现:

for (var i = 1; i <= 10; i++) {
    (function() {
        // Scope begin
        setTimeout(function timer() {
            console.log(i);
        }, 0);
        // Scope close
    })();
}

这样,每一次迭代,timer捕获到的,就是// Scope begin// Scope close之间的作用域了。但这还不够,因为JS不会捕获一个空的作用域

其次,我们在timer捕获的作用域里,保存一份当前i的拷贝:

for (var i = 1; i <= 5; i++) {
    (function() {
        // Scope begin
        var j = i;

        setTimeout(function timer() {
            console.log(j);
        }, 0);
        // Scope close
    })();
}

现在,重新执行一下,就可以看到一开始期待的12345了。当然,我们也可以把上面的代码写成这样:


for (var i = 1; i <= 5; i++) {
    (function(j) {
        // Scope begin
        setTimeout(function timer() {
            console.log(j);
        }, 0);
        // Scope close
    })(i);
}

道理是一样的,函数的参数,实际上也是一个函数的临时变量。

在ES6里,引入了一种在块作用域(block scope)中定义变量的方法:


for (var i = 1; i <= 5; i++) {
    let j = i;  //块作用于变量

    setTimeout(function timer() {
        console.log(j);
    }, 0);
}

let定义的变量,更符合我们在其他编程语种的直觉,每一次迭代,都会有一个全新的变量j,这样,timer每次捕获的,就是当前i的拷贝了。

当然,既然let定义的变量有这个特性,我们可以直接用它来定义for循环,这就和我们印象中的for几乎一样了:

for (let i = 1; i <= 5; i++) { //每次循环都会声明一个新的i,并且赋值为旧的i+1。
    setTimeout(function timer() {
        console.log(i);
    }, 0);
}

可以看做是 let和var的区别之一。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值