JS 是一门单线程的语言,因此,JS 在同一时间只能做一件事,单线程意味着,如果在同个时间有多个任务,这些任务就要排队,前一个任务执行完,才会执行下一个任务。

image.png
举个栗子:

<!-- 脚本 1 -->
<script>
// 同步
console.log('startA')
// 异步宏
setTimeout(() => console.log('A1'), 0)
new Promise((resolve, reject) => {
// 同步
console.log('A2')
resolve()
}).then(() => {
// 异步微
console.log('A3')
})
// 同步
console.log('A4')
</script>

<!-- 脚本 2 -->
<script>
// 同步
console.log('startB')
// 异步宏
setTimeout(() => console.log('B2'), 0)
new Promise((resolve, reject) => {
// 同步
console.log('B2')
resolve()
}).then(() => {
// 异步微
console.log('B3')
})
// 同步
console.log('end2')
</script>

结果:

script A
A2
A4
A3
script B
B2
B4
B3
A1
B1

步骤:

  1. 最开始,JS 引擎将这段代码解析成两个宏任务,一个是 脚本 1,一个是 脚本 2。会把这两个宏任务放到宏任务队列中去。正常情况下,JS 执行是先微后宏,此时微任务队列中没有队列任务,就跳过,去执行宏任务。

image.png

  1. 因为 脚本 1 在上面,JS 会去先执行 脚本 1 ,然后就把 script A 压入 【调用栈】中。
    1. 由于第一行代码是同步代码,所以先执行。
    2. 将 timer1 压入 宏任务队列中

image.png

  1. new Promise() 中 p1 是 同步任务,立即执行,出栈
  2. 触发异步机制,进入 Promise.then() 中,触发回调,把 A3 加入微任务队列中去,等待执行

image.png

  1. 此时,按照“先微后宏“的顺序, 此时,微任务队列中有任务,就把 A3 放入调用栈中执行,输出 A3,出栈(A4 是同步代码,已经在 A3 之前执行输出)

image.png
此时,微任务队列中没有任务了,事件循环会跳过微任务,去执行宏任务。会把 script B 调入调用栈中去(不是全部一下子调入调用栈中的,是按照代码先后顺序去逐行调入的)。
image.png

  1. 将 B1 这个宏任务加入到宏任务队列中去

image.png

  1. B2 是同步代码,加入调用栈中,执行,输出, 出栈;输出 B4,scriptB 宏任务就结束了,调用栈清空。
  2. 触发异步回调 Promise.then(),将 B3 加入到微任务队列中去。
  3. 此时,如图 6,按照先微后宏的顺序,会依次把 B3、A1、B1 输出。

image.png

总结