JS-事件循环机制

JS 是单线程语言,浏览器是多进程多线程应用。

浏览器执行线程

浏览器是多进程的,每一个标签页代表一个独立进程,包含:

  • 浏览器渲染进程,负责页面渲染,脚本执行,事件处理等事务处理,包含的线程有
    • GUI 渲染线程,负责渲染页面,解析 HTML,CSS 构成 DOM 树
    • JS 引擎线程()
    • 事件触发线程
    • 定时器触发线程
    • http 请求线程等

任务队列 Event Queue

所有任务分为同步任务和异步任务。同步任务直接进入 JS 线程执行,异步任务例如异步网络请求或者延时函数,会在当前 JS 线程全部执行完毕后,通过协调查询任务队列,执行剩余的先进入任务队列的异步任务。每次 JS 线程全部执行完毕后都会循环执行上述步骤,这就是我们说的时间循环 Event Loop。

在时间循环中,每一次循环称为 tick,关键步骤如下:

  1. JS 线程为空,开始查询任务队列剩余任务,
  2. 如果不存在剩余任务,线程进入休眠状态,等待再次被浏览器唤醒。
  3. 如果存在,取出先进入的一个任务,放入 JS 线程执行,
  4. 重复第 1 步,开始事件循环。

凡事都有意外之微任务

JS 后来有了类似 Promise 的方法, Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。具体包括 Promise、MutaionObserver、process.nextTick(Node.js 环境)

Promise 实例具有三种状态:异步操作未完成(pending)异步操作成功(fulfilled)异步操作失败(rejected)

Promise 的引入带来了新的问题,假设 Promise.then() 是一个普通异步任务:

Promise.resolve()
    .then(() => {
        console.log(1);
        return 1;
    })
    .then(() => {
        console.log(2);
        return 2;
    })
    .then(() => {
        console.log(3);
        return 3;
    });

setTimeout(() => {
    console.log(4);
}, 0);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  1. 拥有多个 then 的 Promise 对象异步任务进入任务队列。
  2. 其他异步任务进入任务队列。
  3. 第一个 Promise.then() 异步任务进入 JS 线程执行。
  4. 其他异步任务进入任务队列遵循时间循循环机制,阻塞下一个 Promise.then() ,优先被执行。
  5. 最后才轮到下一个 Promise.then() 进入 JS 线程执行。
  6. 最后预计输出:1 4 2 3

以上步骤就造成一问题:Promise 的链式 then() 回调迟迟得不到执行,造成数据混乱,应用卡顿,Promise 不再是一个整体。 为了解决以上问题:异步任务被分为了 宏任务 和 微任务,宏任务还是原来的时间循环机制,但是微任务拥有独立的微任务队列,当 JS 线程空闲,会先查找微任务队列,当执行完所有微任务(包含微任务队列循环过程中临时插入队列的微任务)后,再执行宏任务。

最终正确的输出结果应该是 1 2 3 4

异步任务分类

  • 宏任务主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
  • 微任务主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)

练习一下

console.log(1);

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

Promise.resolve()
    .then(function () {
        console.log(3);
    })
    .then(function () {
        console.log(4);
    });

console.log(2);

// 正确顺序:12345
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log(1);

setTimeout(function () {
    console.log(5);
}, 10);

new Promise((resolve) => {
    console.log(3);
    resolve();
    setTimeout(() => console.log(6), 10);
}).then(function () {
    console.log(4);
});

console.log(2);

// 正确顺序:123456
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17