事件循环、宏任务与微任务

简单介绍 JavaScript 的事件执行

让我们从一段简单的代码开始:

1
2
3
4
5
6
7
8
9
10
11
12
function fn1() {
console.log(1)
}
function fn2() {
console.log(2)
}
function fn3() {
console.log(3)
}
fn1()
fn2()
fn3()

上面这段代码一目了然,js 在执行到这段代码的时候会先声明三个函数,然后依次执行,最后的输出结果为 123。你把它想象成核酸检测的长长的队伍,js 的执行就是一个检测窗口,所有的人(代码)排列在那里,依次检测(执行),然后离开。这是 js 主线程在接收到执行这段代码的信息的时候做的事情。
上面那段代码我们改一下:

1
2
3
4
5
6
7
8
9
10
11
12
function fn1() {
console.log(1)
}
function fn2() {
fn1()
console.log(2)
}
function fn3() {
fn2()
console.log(3)
}
fn3()

在 fn2 和 fn3 中添加了对其他函数的调用,此时调用 fn3 ,应该是怎么样的呢?
fn3 入栈,fn2 入栈,fn1 入栈,fn1 调用结束,输出 1,fn2 调用结束,输出 2,fn3 调用结束,输出 fn3,任务结束。
js 主线程是一个先进先出的任务队列;在调用函数的时候被调用的函数进入调用栈,后进先出。这是 js 执行的基本概念。

事件循环机制的深入探讨

上面我们讲了 js 执行的基本情况,但如果有些操作需要等很久呢?
因为 js 是单线程、非阻塞的,如果这些需要等很久的操作不被搁置的话,那就意味着线程被阻塞住了。因此在遇到这些操作的时候,js 会主动把它们挂起来,等到合适的时机再继续执行。
那么什么是合适的时机呢?
在需要等很久的操作(异步操作)终于有了结果的时候,js 把这个操作挂到任务队列上,等到当前任务执行完毕的时候,就去任务队列上把这个任务取出来继续执行。
把上面的例子再稍微改一下:

1
2
3
4
5
6
7
8
9
10
11
function fn1() {
console.log(1);
}
function fn2() {
setTimeout(() => {
console.log("settimeout");
fn1();
});
console.log(2);
}
fn2()

在执行 fn2 的时候先遇到了 setTimeout,然后把这个 setTimeout 先挂起来,等到 fn1 执行完毕之后再执行这个 setTimeout。
所以设置 setTimeout 延时多少秒,最后实际的延时时间与设置的并不一致,是因为设置的延时时间只是告诉 js 引擎在这一段时间过后,把这个 setTimeout 内的函数加到执行队列里去,等到当前任务执行完毕后再执行。这个当前任务执行的时间,就是实际延时时间与设置的延时时间之间的差值。
这些需要被挂起的任务分为宏任务和微任务,其中 setTimeout、setInterval 是宏任务,Promise.then 里边是微任务。
js 执行时先执行微任务队列,等到微任务队列清空之后再执行宏任务,如此循环往复,直到主线程中所有任务执行完毕。这就是 js 的事件循环机制。
最后,让我们以一个例子来分析一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function test() {
console.log(1)
new Promise((resolve) => {
console.log(2)
resolve()
}).then(() => {
console.log(3)
})
setTimeout(() => {
console.log(4)
}, 0)
new Promise((resolve) => {
console.log(5)
resolve()
}).then(() => {
console.log(6)
})
}
test()

主线程执行 test 方法,先调用 console.log(1),接着执行 Promise 里的 console.log(2),在执行到 resolve() 的时候说明这个 promise 已经结束了,那么把 then 里面的内容放到微任务队列。
继续执行 setTimeout,0 秒后放到宏任务队列。
执行 Promise 里的 console.log(5),然后 resolve,把 then 里面的内容放到微任务队列。
至此任务队列里的代码执行完毕,开始执行微任务队列代码,先是 console.log(3),接着是 console.log(6)
微任务队列清空。开始从宏任务队列取出任务来执行,即 console.log(4)
所以最终的执行结果是 125364。

参考资料

WHATWG event loops
JS事件循环之宏任务与微任务

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2018-2023 文初阳
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信