什么是 Event Loop?

JavaScript 是一门单线程、非阻塞的语言,它只有一个调用栈,每次只能做一件事情。 它的异步和多线程就是靠 Event Loop 事件循环机制来实现的。
JavaScript 代码是从上往下一行一行执行的。先将同步代码执行完,再执行异步代码。

Event Loop

组成部分:

  • 调用栈(call stack)
  • 任务队列(Task Queue)
  • 微任务队列(Microtask Queue)

Event Loop

如下这段代码依次输出 2 -> 1 -> 3

1
function f1() {
2
    console.log(1)
3
}
4
5
function f2() {
6
    console.log(2)
7
    f1()
8
    console.log(3)
9
}
10
11
f2()

执行顺序:

  1. f2() 压入调用栈,执行里面的代码
  2. console.log(2) 压入调用栈并执行,打印 2,然后弹出
  3. f1() 压入调用栈,执行里面的代码
  4. console.log(1) 压入调用栈并执行,打印 1,然后弹出
  5. f1() 执行完毕弹出
  6. console.log(3) 压入调用栈并执行,打印 3,然后弹出
  7. f2() 执行完毕弹出

任务队列

JavaScript 中的异步操作,例如 fetch、事件回调、setTimeout、setInterval 的回调函数会入队到任务队列。

看如下示例

1
function f1() {
2
    console.log(1)
3
}
4
5
function f2() {
6
    setTimeout(() => {
7
        console.log(2)
8
    })
9
    f1()
10
    console.log(3)
11
}
12
13
f2()

执行顺序:

  1. f2() 压入调用栈,执行里面的代码
  2. setTimeout 压入调用栈时,它的回调会进入到任务队列,
  3. f1() 压入调用栈,执行里面的代码
  4. console.log(1) 压入调用栈并执行,打印 1,然后弹出
  5. f1() 执行完毕弹出
  6. console.log(3) 压入调用栈并执行,打印 3,然后弹出
  7. f2() 执行完毕弹出
  8. 当调用栈为空时,任务队列的消息被压入到调用栈中,并执行,打印 2 , 然后弹出

微任务队列

Promise、async、await 会加入到微任务队列中,会在调用栈清空时立即执行,并且新加入的微任务也会一起执行,

1
var p = new Promise((resolve) => {
2
    console.log(4)
3
    resolve(5)
4
})
5
6
function f1() {
7
    console.log(1)
8
}
9
10
function f2() {
11
    setTimeout(() => {
12
        console.log(2)
13
    })
14
    f1()
15
    console.log(3)
16
    p.then((resolved) => {
17
        console.log(resolved)
18
    }).then(() => {
19
        console.log(6)
20
    })
21
}
22
23
f2()

执行顺序:

  1. new Promise 构造函数,被压入调用栈
  2. console.log(4) 压入调用栈并执行,打印 4,然后弹出
  3. resolve(5) 压入调用栈并执行,然后弹出
  4. f2() 压入调用栈,执行里面的代码
  5. setTimeout 压入调用栈时,它的回调会进入到消息队列
  6. f1() 压入调用栈,执行里面的代码
  7. console.log(1) 压入调用栈并执行,打印 1,然后弹出
  8. f1() 执行完毕弹出
  9. console.log(3) 压入调用栈并执行,打印 3,然后弹出
  10. p.then(resolved => {console.log(resolved)}) 压入调用栈并执行,回调函数入队到微任务队列,然后弹出
  11. .then(() => { console.log(6)}) 压入调用栈并执行,回调函数入队到微任务队列 ,然后弹出
  12. f2() 执行完毕弹出
  13. console.log(resolved) 压入调用栈并执行,打印 5,然后弹出
  14. console.log(6) 压入调用栈并执行,打印 6,然后弹出
  15. 当调用栈为空时,消息队列的消息被压入到调用栈中,并执行,打印 2 , 然后弹出