首页 > 关于js闭包的问题

关于js闭包的问题

for (var i=1;i<=5;i++){
        setTimeout(function () {
            console.log(i);
        },1000);
    }
    
为什么这么段代码最终的输出是5个6,而不是想要的1,2,3,4,5。

改成:

for (var i=1;i<=5;i++){
        setTimeout(function () {
            console.log(i);
        },0);
    }
输出的也还是5个6。。
为什么啊?

可以用生动形象的例子解释一下闭包吗?有点似懂非懂

闭包确实是个太难以理解的概念,理论上来说,只要外部函数访问另一函数内部的变量都称作闭包。
在上面的程序中,闭包自然是成立的console.log()是在另一个外部函数中执行的。但因为它捕获的变量是i,而变量i在循环执行完毕后就变成了6,所以自然就输出了5个6。
如果想真正的捕获循环变量,必须加一个中间变量,代码可以这样写:

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

对于,setTimeout(...,0),还是输出5个6的问题,这是因为循环执行的速度太快了,定时器内的代码还没执行呢,它的优先级是很低的。


可能你理解成的是

setTimeout(function () {console.log(1|2|3|4……)}

但实际上这里面的i储存的是i的引用。相当于:

var i = 0;
setTimeout(function() {
    console.log(i);
  }, 1000);
setTimeout(function() {
    console.log(i);
  }, 1000);
setTimeout(function() {
  console.log(i);
}, 1000);
setTimeout(function() {
  console.log(i);
}, 1000);
setTimeout(function() {
  console.log(i);
}, 1000);
i = 5;

所以最后结果都是5。

setTimeout((function(num) {
    return function (){
      return console.log(num);
    };
  }(i)), 1000);

这就是闭包,这里把闭包放在了匿名函数里面,由于函数参数是值传递,就把i的当前值传递给了num。


这个问题要分成两部分来解答

  1. 延时代码为什么输出 6

  2. 延时为 0 的代码为什么没有立即执行


首先是第一个,为什么代码输出 5 个 6,你谈到的了闭包的问题,闭包确实是解决这个问题的方法。要理解闭包,我们先仔细分析一下这段代码:
在循环执行完后,这段代码涉及了 6 个作用域,分别是 for 循环所在的作用域,以及循环中创建的 5 个函数的作用域,而循环变量 i 的作用域是 for 循环所在的作用域,5 个函数的作用域中都不存在 i 这个变量。因此,当函数执行时,所有对变量 i 的访问根据规则都要沿作用域链去上一层作用域去查找——即 5 个函数访问到的均是 for 循环所在作用域中的 i 变量。而在访问这个变量的时候循环已经执行完毕,此时 i 的值为 6,因此我们会得到 5 个 6。
接下来我们使用闭包来解决这个问题:

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

可以看到,我们在循环中增加了一个立即执行方法,这样在循环结束后,这段代码涉及的作用域变成了 11 个,除了刚才讲到的 6 个,另外 5 个是循环中的立即执行函数生成的作用域,并且其中保存了循环过程中 i 的值(用 currentI 变量分别保存在 5 个作用域中),更重要的是这次延时函数的作用域链上层作用域就是立即执行函数的作用域,所以我们就顺利得到了期望的结果。
至于闭包的概念,宽泛来讲,闭包其实就是一个作用域,可以用来保存变量,仅此而已。


第二,为什么 setTimeout 为 0 的定时器没有立即执行,这就涉及到 JavaScript 事件循环的概念,这个概念的具体定义可以自己搜索一下。这里简单直观的理解,可以认为所有的定时器任务都要被推入一个队列中等待执行,而这个队列最早开始执行也要等到 JavaScript 执行完正在执行的代码,所以说 setTimeout 的代码不可能穿插在循环中间执行,而只能等到循环结束才能执行,这时候又变成了第一段代码的情况。

【热门文章】
【热门文章】