for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
//输出结果:6 个数字 6
为什么会输出错误的结果而不是从1到6呢?
原理:JavaScript是单线程执行的,而setTimeOut函数是异步函数,它会先被放到单线程的事件队列中,在指定的事件间隔后,才会执行这个函数。但是for循环会继续执行下去,因为他并不是异步的,它会立即执行并在短时间内完成所有的迭代,所以在这个循环中每次都会设定一个定时器,并且在指定的时间后,进行日志的打印,而这时所有的迭代都已完成,所以会出现6个6这样的结果。
解决方法:
1.闭包
for (var i = 1; i <= 5; i++) {;
(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
原理:在传入函数时,传入的函数会记录当时的j的值,等到setTimeOut执行这个函数时,就可以使用到外面的function所记录的j
2.使用setTimeOut的第三个参数,传入函数(其实原理也是闭包)
for (var i = 1; i <= 5; i++) {
setTimeout(
function timer(j) {
console.log(j)
},
i * 1000,
i
)
}
原理其实和前面的差不多,具体来说,当 setTimeout 的第一个参数是一个函数时,额外的参数会按顺序传递给这个函数。在这种情况下,将 i 作为第三个参数传递给 setTimeout,然后在回调函数中接收这个参数作为 j。由于 JavaScript 中的函数是闭包,回调函数会捕获传递给它的参数值,从而避免了由于循环结束后 i 的值变为 6 而导致的问题。
3.不用var使用let
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
原理:当使用 var 声明循环变量时,该变量存在于整个函数作用域而不是块级作用域。因此,每次迭代时 i 的值会被更新,并且所有的定时器回调函数实际执行时,它们引用的是同一个 i 变量,而这个变量在循环结束后的值是 6,使用 let 声明的变量具有块级作用域,每次迭代都会创建一个新的作用域,因此在每个定时器的回调函数中,i 的值会被正确捕获并保留,而不会受到循环结束后 i 变为 6 的影响。