for循环与setTimeout、事件绑定

闭包

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

问题

//循环中为不同的元素绑定事件,事件回调函数里如果调用了跟循环相关的变量,则这个变量取循环的最后一个值
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))
}

 

posted @ 2019-05-16 10:07  冬日里的飘雪  阅读(435)  评论(0编辑  收藏  举报