JavaScript 函数的执行时机

222 阅读2分钟

为什么打印6个6

今天让我们通过一个例子学习一下JavaScript 函数的执行时机,代码如下:

let i = 0
for(i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}//打印出6个6

image.png

要理解这个代码,先来解释setTimeout 0有什么作用:

setTimeout函数用来指定某个函数或某段代码在多少毫秒之后执行,它接受的前两个参数为要执行的代码和以毫秒表示的时间。

举个栗子:

setTimeout(function() {
      console.log("aaa")
},1000);//返回2,一秒钟后打印出aaa

image.png

返回的整数2表示这个定时器的编号,以后可以用来取消这个定时器,但实际任务中,很少这么用。

setTimeout函数的重点是第二个参数,第二个参数是一个表示等待多长时间的毫秒数,但是经过该时间后的代码不一定会立刻执行

要理解这句话之前先说到今天的主题了,JavaScript的运行机制:

JavaScript是一个单线程的解释器,一定时间之内只能执行一段代码。

为了控制要执行的代码,就有一个JavaScript任务队列。这些任务会按照将它们添加到队列的顺序执行。

setTimeout()的第二个参数告诉JavaScript再过多长时间把当前任务添加到队列中。也就是说setTimeout()指定的任务肯定是最后一个添加到队列中的,这个指定的任务要等到前面的任务执行完了以后再执行。

所以setTimeout函数的第二个参数应该理解为等到前面的任务执行之后再经过指定的毫秒数后来执行当前的任务

依旧举个栗子来验证:

console.log(1)
setTimeout(function() {
       console.log(2)
},100);
let t1 = Date.now()
for(var i=0;i<10000;i++){
      console.log(3);
}
let t2 = Date.now()
console.log(t2-t1);

GIF 2021-12-3 22-29-30.gif

从运行结果中看到,console.log(2)确实是最后运行,而且是等到完成了1000次for循环的后再运行的,而不是之前理解的100ms之后会立即运行。

那么最后解释setTimeout 0就比较容易了,指的是setTimeout指定的任务在现有的任务执行之后立即执行。

有了以上的概念再来理解我们开头的代码,就能明白为什么打印出6个6了:

let i = 0
for(i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}//打印出6个6

在这段代码中,通过for循环有6个console.log(i)的任务被安排在了最后执行,而变量i是在for循环的外部定义的,通过循环后i已赋值为6,最后的结果自然就是6个6。

打印出0、1、2、3、4、5 的方法

解决了6个6的问题后,我们再来看看怎么打印出0到5,

方法1

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

在ES6中,对letfor语句做了优化,可以使得以上代码打印出0到5,具体的机制可以参考我用了两个月的时间才理解 let

方法2

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

image.png

这个方法使用立即执行函数来形成局部作用域,使得每次循环的i值分开储存。

方法3

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

image.png

i值作为setTimeout的第三个参数传入过去。此时i值作为函数的参数,自然分开保存。