美文网首页JavaScript 进阶营程序员
探讨循环与回调函数问题

探讨循环与回调函数问题

作者: 老邵 | 来源:发表于2018-04-02 20:52 被阅读61次

    循环与回调函数的问题,也就是作用域与闭包的问题。个中原因是 JavaScript 的两个特性:

    1. JavaScript 不支持块作用域
    2. 只要使用了回调函数就是在使用闭包

    第一个特性就是字面的意思,第二个特性是由闭包的特性引申出来的。

    从实际代码的角度来讲,闭包是在定义时所在作用域以外执行的函数。而 js 引擎在执行 js 代码,就会将回调函数丢给事件触发线程。当回调函数执行时,回调函数是在定义时所在作用域以外,所以说只要使用了回调函数就是在使用闭包。

    弄清楚了概念,来看一下循环与回调函数的问题,以下方代码为例:

    var btns = document.querySelectorAll("button");
    for(var i = 0; i  <  btns.length;i++){//假设 btn.length 为 6
    btns[i].onclick = function() {
                  console.log(i);
          }
    }
    

    这段代码中,打印 i 所在的匿名函数是一个回调函数。当按钮点击时该函数就会触发,打印循环时的 i。然而结果出人意料,每次点击时输出的数字都是 6,为什么会这样呢?

    首先是因为 js 没有块作用域,for 循环多次迭代中只是声明了一个 i,所以最后 i 只有一个值,也就是循环结束时的值。

    其次是因为回调函数是闭包,所以在作用域外引用作用域中的 i,这个 i 只有一个最终值。

    这个例子也可以换一个角度来想。如果 js 支持块作用域,那么上方代码中每个按钮的回调都声明在不同的块作用域中。只要每次迭代保存一下 i 的值,如使用 var j = i,那么每次打印 j,就可以打印 i 的不同值了。

    根据上方的原因分析,我们可以得出解决方案——使 i 的值声明或保存在不同的作用域中。具体方法多种多样,下面列举两个解决方法:

    1. var btns = document.querySelectorAll("button");
       for(let i = 0; i  <  btns.length;i++){//假设 btn.length 为 6
       btns[i].onclick = function() {
                   console.log(i);
             }
       }
      

    let 是 ES6 的关键字,用来声明一个块级作用域的本地变量,并使变量每次迭代时声明一次。

    1. var btns = document.querySelectorAll("button");
       for(var i = 0; i  <  btns.length;i++){//假设 btn.length 为 6
           (function(){
              var j = i;
              btns[i].onclick = function() {
                          console.log(j);
                  }
             })();
       }   
      

    (function(){})()是一个立即执行函数,每次迭代中都有一个不同的函数作用域,在其中保存 i 的当前值并使用。

    James Harris 2017-01-20 08-48-05 .jpg

    相关文章

      网友评论

        本文标题:探讨循环与回调函数问题

        本文链接:https://www.haomeiwen.com/subject/ezgncftx.html