阅读这段代码
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 版权协议,转载请附上原文出处链接和本声明。