for 循环中的setTimeout(function(){})异步问题

  • Post author:
  • Post category:其他


阅读这段代码

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

预期是分别输出数字1-5, 每秒1次,每次1个

结果却是每秒一次输出了5个6



1. setTimeout的执行原理

setTimeout()是一个异步方法, 传递一个函数,

延迟一段时候把该函数添加到队列当中,并不是立即执行,

而且必须等当前环境所有代码执行完以后, 才会运行

也就是说我们执行这个for循环的时候

    setTimeout(fun(...), 1000)
    setTimeout(fun(...), 2000)
    setTimeout(fun(...), 3000)
    setTimeout(fun(...), 4000)
    setTimeout(fun(...), 5000)

五个函数先进入了队列, 然后等for循环结束后再依次出队 (粗略理解, 实质上是回调函数)

for循环结束后, 此时i是等于6的, 所以每秒一次输出了5个6

要注意到var定义的i实质上是全局变量, 等同于下面的代码

    var i
    for (i = 1; i <= 5; i++) {
        ...
    }



2. 解决办法

其实还是词法作用域的问题,我们从作用域下手,解决这个问题



2.1 使用闭包

这个办法的原理是创建了闭包作用域, 每次循环会生成一个新的闭包作用域, 使得延迟函数回调可以访问到正确的值

注意, 闭包作用域里必须声明变量j, 如果是一个空的作用域, 那不会产生作用

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



2.2 使用ES6中的let

这个办法的原理是通过let来劫持块作用域, 注意, 这个变量i不只会声明一次, 每次迭代的时候都会声明i, 每次迭代后, i的值都会使用

上一个迭代的值来初始化这个变量, 形成一个块作用域

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



版权声明:本文为qq799028706原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。