《JS 函数的执行时机》

243 阅读2分钟

为什么如下代码会打印 6 个 6

let i = 0
for(i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}

在解释为什么是 6 个 6 之前,先了解一下 setTimeout 函数的作用,举个形象点的栗子:

正当小明打游戏快推爆对面水晶的时候,他妈这时候叫他吃饭,这时候他先答应他妈马上去吃饭,接着又继续打一会直到游戏结束再去吃。这里的马上去就相当于 setTimeout 函数的机制,吃饭就是 console.log(i) ,但实际上还是先完成当前s手头上的事情(即 for 循环)之后,再尽快去吃饭。不过为什么会这样呢?这跟 JS 函数的执行机制有关, 要知道 setTimeout 是一个异步的函数,那么这段代码的执行就变成了,先走完整个循环,这时 i 已经变成了 6,才开始执行 6 个 console.log(i),所以最终只会打印出 6 个 6。

写出让上面代码打印 0、1、2、3、4、5 的方法

那有没有什么办法能够解决这个问题, 我就想这样,然后还要打印出 0 1 2 3 4 5 。

回答是有的,只需要稍稍改变一下就可以了。

只要把 let i = 0 放进 for 里面就可以实现了。这是JS为了迎合新手玩家而发明的方法。

for(let i = 0; i < 6; ++i) {
	setTimeout(() => {
		console.log(i)
	}, 0)
}

那对上面的代码要怎么给出一个合理的解释呢

因为在 for 循环里用 let 声明 i 的话,每次执行 for 循环,其实都会创建一个新的 i(只是看不到),这个 i 会保留当前 i 的值,所以不管你过多久打印 i,当时的 i 已经被保存了下来,所以会依次打印出 0,1,2,3,4,5

除了使用 for let 配合,还有什么其他方法可以打印出 0、1、2、3、4、5

1. 可以通过自执行函数来实现

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

把 setTimeout 函数放在一个立即执行函数里面,这样立即执行函数就创建了一个新的作用域把 setTimeout 函数包裹了起来,并且用 j 捕获每次循环时的 i这样,打印出来的 i 就是每次循环时的 i。

2. 通过setTimeout第三个参数

  for (var i = 0; i < 6; i++) {
    setTimeout((i) => console.log(i), 0, i);
  }