setTimeout 循环输出问题

解决 for 循环中 setTimeout 输出问题的三种方法

问题

改造下面的代码,让它正确输出 1, 2, 3, 4, 5:

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

原代码会输出 5 个 6,因为 var 声明的变量没有块级作用域,所有定时器共享同一个 i 变量。

解答

方法一:使用 IIFE

通过立即执行函数创建独立作用域,每次循环时将当前的 i 值传入:

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

方法二:setTimeout 第三个参数

setTimeout 的第三个参数会作为回调函数的参数传入:

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

方法三:使用 let

let 声明具有块级作用域,每次循环都会创建新的变量:

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

关键点

  • var 声明的变量没有块级作用域,循环中的异步操作会共享同一个变量
  • IIFE 通过创建独立作用域来保存每次循环的变量值
  • setTimeout 第三个参数可以向回调函数传递参数
  • let 声明具有块级作用域,是最简洁的解决方案