1

概念

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

例子

问题

下面的代码的运行结果和代码语意上表达的不相符,我们希望它能够每隔一秒输出一次,每次输出对应的数字,即第一秒后输出1,第二秒后输出2......
而这段代码的运行结果是,第一秒后输出6,第二秒后输出6......
请解释原因并且提出修改方案。(包含要点,函数作用域,块作用域,闭包,let)

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

答案

解释原因

  1. var i,实际上声明了一个全局变量

  2. 延迟函数timer必然是在循环结束后才开始执行,循环结束后,i=6

  3. 循环中确实定义了多个延迟函数timer,延迟函数在setTimeout的内部被回调,根据闭包概念,timer在其声明之外的地方被调用,timer能够记住并访问其声明位置的词法作用域,存在闭包

  4. 实际上timer所记住的词法作用域就是全局作用域,所以引用输出的i都是6

修改方案

  1. 只要能保证每次循环都能够创建新的作用域,在新作用域中保存当前i的值即可

  2. 所以任何可以创建新作用域的方法都可以达到效果,具体可参考这里,通过分析这段代码的进化历程,或许能够加深您对JavaScript的作用域的理解

常见的做法有

  1. 利用具名立即执行函数,每次循环都创建新作用域

for (var i = 1; i <= 5; i++) {
    (function scope(j) {
        setTimeout(function timer() {
            console.log(j);
        }, i * 1000);
    })(i);
}
  1. 利用es6 let创建块作用域

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

总结

根据闭包的概念,只要有回调就会有闭包......
记住这个例子,还怕被问闭包?

后记

最近写了三篇,参考书籍主要是《你不知道的JavaScript(上卷)》,看完这三篇可以说就相当于看了半本书了,^_^
前端框架层出不穷,不反对使用框架,但一定要有头头能驾驭的了它。试想,做前端的人那么多,如果冰河世纪来临,存活下来的还是那些会写框架的人,那些只懂框架使用的人就危险了
我快毕业了,但心里慌慌的,所以如果您刚入门,建议还是扎扎实实地学基础,其实JS基础远比您想象的要复杂,千万别以为自己看了W3C School就觉得自己Ok了,反正我是要再努力下,争取看得懂牛人的代码哈
下篇见......


Myou_Aki
415 声望35 粉丝