Post

JavaScript Event Loop

How the call stack, task queue, and microtask queue interact to make JavaScript's single-threaded async model work.

这个问题很难说清楚,我就记录自己的理解。

JavaScript 是单线程的,同一时刻只能干一件事。「事件循环」是浏览器渲染主线程的工作方式,协调同步代码和异步回调的执行顺序。它的组成部分:

执行栈(Call Stack):当前正在运行的代码所在的地方。函数调用入栈,执行完出栈。栈清空之前,事件循环不会处理其他任务。

任务队列(Task Queue)setTimeoutsetInterval、I/O 操作的回调在这里排队,等执行栈空了才会被取出执行。

微任务队列(Microtask Queue)Promise.then()MutationObserver 的回调在这里。优先级比任务队列高——每次执行栈清空后,先把微任务队列清空,再去取下一个普通任务

W3C 已经不再使用「宏任务」这个词,直接叫「任务」。


几个例子过一遍,顺序就清楚了。

例子 1

1
2
3
4
5
6
7
8
9
10
11
console.log("Start");

setTimeout(() => {
  console.log("Timeout Task");
}, 0);

Promise.resolve().then(() => {
  console.log("Micro Task");
});

console.log("End");

同步代码先跑:打印 StartsetTimeout 回调入任务队列,Promise.then 回调入微任务队列,打印 End。栈清空后先清微任务队列:打印 Micro Task。再取任务队列:打印 Timeout Task

输出:StartEndMicro TaskTimeout Task

例子 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log("A");

setTimeout(() => {
  console.log("B");
}, 0);

Promise.resolve()
  .then(() => {
    console.log("C");
  })
  .then(() => {
    console.log("D");
  });

console.log("E");

同步:AE。微任务队列清空:C.then 返回的新 Promise 又入微任务队列)→ D。任务队列:B

输出:AECDB

例子 3(嵌套):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
console.log("A");

setTimeout(() => {
  console.log("B");

  Promise.resolve()
    .then(() => { console.log("C"); })
    .then(() => { console.log("D"); });
}, 0);

Promise.resolve()
  .then(() => {
    console.log("E");

    setTimeout(() => {
      console.log("F");
    }, 0);

    Promise.resolve().then(() => {
      console.log("G");
    });
  })
  .then(() => {
    console.log("H");
  });

console.log("I");

同步:AI。清微任务:E(顺带把 F 的 setTimeout 放进任务队列,G 的 Promise 入微任务队列)→ GH。取第一个任务(B 的 setTimeout):B,执行时产生 C/D 的微任务,先清微任务:CD。再取任务:F

输出:AIEGHBCDF

例子 4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
console.log("1");

function asyncFunction() {
  console.log("2");

  setTimeout(() => {
    console.log("3");
    Promise.resolve().then(() => { console.log("4"); });
  }, 0);

  Promise.resolve().then(() => { console.log("5"); });
}

asyncFunction();

Promise.resolve().then(() => { console.log("6"); });

console.log("7");

同步:12(asyncFunction 内)、7。清微任务:5(asyncFunction 内的 Promise)→ 6(外部的 Promise)。取任务(3 的 setTimeout):3,清微任务:4

输出:1275634


规律就是:同步代码跑完 → 清空微任务 → 取一个普通任务 → 清空微任务 → 再取下一个普通任务……如此循环。微任务在每次任务之间都会被完全清空,这是它优先级高于普通任务的本质原因。

This post is licensed under CC BY 4.0 by the author.