美文网首页
for循环与setTimeout、事件绑定

for循环与setTimeout、事件绑定

作者: iluvleleyo | 来源:发表于2016-11-09 23:58 被阅读0次

    闭包

    闭包 = 函数 + 创建该函数的环境

    问题

    //循环中为不同的元素绑定事件,事件回调函数里如果调用了跟循环相关的变量,则这个变量取循环的最后一个值
    for (var i = 0; i < helpText.length; i++) {
        var item = helpText[i];
        document.getElementById(item.id).onfocus = function() {
          showHelp(item.help);
        }
      }
    for (var i = 0; i < 5; i++) {
        setTimeout(function(){
            console.log(i)
        },10)
    }
    

    这些问题出现的原因,闭包不是主因,是由于setTimeout和事件绑定的机制造成的。
    setTimeout是从任务队列结束的时候开始计时的,如果前面有进程没有结束,那么它就等到它结束再开始计时。在这里,任务队列就是它自己所在的循环。循环结束setTimeout才开始计时,所以无论如何,setTimeout里面的i都是最后一次循环的i。

    解决方案

    setTimeout

    setTimeout第一个参数需要一个函数,所以返回一个函数给它,返回的同时把i作为参数传进去,通过形参v缓存了i,并带进返回的函数里面。(算是闭包)

    for (var i = 0; i < 5; i++) {
        var a = function(v){
            return function(){
                console.log(v)
            }
        }
        setTimeout(a(i),0)
    }
    
    for (var i = 0; i < 5; i++) {
        (function(j){
            setTimeout(function(){
                console.log(j);
            },0)
        })(i);
    }
    

    解决方法还可以用let——使用 let 来声明块变量,这时候变量就能作用于这个块所以能够保存下来。

    • 总结
      setTimeout的机制:在for循环的时候,settimeout在挂回调,但是,得等到任务队列的最后,也就是for循环结束的时候才执行回调。所以,如果用var的话,var不是块级作用域,var的值在任务最后就是for循环结束的时候,已经变成5了。

      解决的中心思想就是保存变量,保存变量、保存状态的方法是用闭包。而let的方法(let底层实现也是用闭包。。。),块作用域,和传形参保存变量是一样的,相当于在挂回调的时候包了一层,挂回调的时候比如创建了很多j,在真正调回调的时候,调用的不是i而是保存的j,所以保存下来了。let i在for循环的每次叠代都为i创建新的绑定。

    let翻译

    for (let i = 0; i < 5; i++) {
        setTimeout(function(){
            console.log(i)
        },10)
    }
    
    // babel翻译之后
    var _loop = function _loop(i) {
        setTimeout(function () {
            console.log(i);
        }, 10);
    };
    
    for (var i = 0; i < 5; i++) {
        _loop(i);
    }
    

    绑定事件

    for (var i = 0; i < 5; i++) { 
        var a = function(){
            console.log(i) 
        }
        document.body.addEventListener('click',a) 
    }
    

    事件是需要触发的,而绝大多数情况下,触发的时候循环已经结束了,所以循环相关的变量就是最后一次的取值。
    为了解决,通过函数的实参传进函数体。

    for (var i = 0; i < 5; i++) { 
        var a = function(v){
            return function(){
                console.log(v)
            }
        }
        document.body.addEventListener('click',a(i))
    }
    

    相关文章

      网友评论

          本文标题:for循环与setTimeout、事件绑定

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