JavaScript 的事件循环

简单了解一下浏览器的 js 事件循环,由于涉及到浏览器版本不一样结果不一样,既参考。

首先要理解一下:

  1. js 的执行分同步与异步
  2. 在执行 js 时首先执行整体代码的同步代码,然后异步代码处理完后会把回调函数放置到任务队列里等待执行栈执行完后,将任务队列的任务添加到执行栈里执行
  3. 事件循环是通过任务队列的机制进行协调的,一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。 setTimeout/Promise 等 API 便是任务源,而进入任务队列的是他们指定的具体执行任务。
  4. 任务队列的执行顺序又分为宏任务队列和微任务任务,微任务队列都在当前宏任务执行完毕之后立刻执行,然后才会执行浏览器的渲染。
js事件循环
js事件循环

宏任务

宏任务可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
宏任务主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI 交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)

微任务

微任务可以理解是在当前宏任务执行结束后立即执行的任务。也就是说,在当前宏任务任务后,下一个宏任务之前,在渲染之前。

所以它的响应速度相比 setTimeout(setTimeout 是宏任务)会更快,因为无需等渲染。也就是说,在某一个宏任务执行完后,就会将在它执行期间产生的所有微任务都执行完毕(在渲染前)。

微任务主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)

运行机制

  1. 执行当前宏任务(当前宏任务从任务队列里获取)
  2. 在执行栈中遇到微任务,把它放置到微任务队列里,遇到宏任务把它放置宏任务队列
  3. 当前执行的宏任务结束,检查微任务队列,如果有任务,则有序的执行微任务,否则执行下一步
  4. 浏览器开始检查渲染,然后 GUI 线程接管渲染
  5. 渲染完毕后,js 线程接管,继续重第一步执行代码
运行机制
运行机制

以下为测试:

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
28
29
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
//async2做出如下更改:
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
}
console.log('script start');

setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();

new Promise(function(resolve) {
console.log('promise3');
resolve();
}).then(function() {
console.log('promise4');
});

console.log('script end');

输出结果:

1
2
3
4
5
6
7
8
script start
async1 start
promise1
script end
promise2
async1 end
promise4
setTimeout