Skip to content
大纲

事件循环

JS 是一个非阻塞单线程的语言,单线程确保流程的执行顺序,非阻塞指异步操作会被挂起而优先执行同步操作;
JS 内部会一直接受消息队列,当事件触发时, 会添加对应的消息至消息队列中, 消息会按照队列中的顺序依此执行。

  • 同步任务在主线程上执行,形成 执行栈
  • 等待任务的回调结果进入 任务队列
  • 当主线程中同步任务执行完后才读取 任务队列,任务队列 中的异步任务进入主执行栈
  • 异步任务执行完毕后进行下一个循环

JS 的并发模型基于事件循环

运行时概念

  • 堆(Heap): 存放对象等大块非结构化的内存区域
  • 栈(Stack): 函数调用形成一个栈帧, 函数内部调用的函数会依此入栈
  • 队列(Queue): 事件循环通过消息队列处理, 每个消息对应与之关联的函数

添加消息(任务)

  • 触发事件时,会为对应的监听器添加消息至消息队列

任务队列中的异步任务分为两种

  • 微任务
    • Promise
    • MutationObserver
    • process.nextTrick
  • 宏任务
    • setImmediate(node support)
    • setInterval
    • setTimeout
    • MessageChannel
    • postMessage
    • I/O
    • UI render

执行顺序: 当前执行栈执行完毕时优先处理微任务队列中的事件,再取宏任务队列中的事件

事件冒泡:微任务的优先级高于事件冒泡,宏任务的优先级低于事件冒泡

示例代码

js
setTimeout(() => {
  console.log("timeout 1");
}, 1000);
const prev = +new Date();
console.log(1);
while (new Date() - prev < 2000) {}
setTimeout(() => {
  console.log("timeout 2");
}, 0);
new Promise(resolve => {
  console.log(2);
  resolve();
}).then(() => console.log("promise"));
console.log(3);
// > 1
// > 2
// > 3
// > promise
// > timeout 1
// > timeout 2

说明

  • setTimeout 接受的参数表示待加入队列的消息
  • 延迟时间指队列中消息都处理完毕后等待的最小时间

分析

  1. 第一轮宏任务中执行同步代码:1 -> 2 -> 3
  2. 第一轮宏任务结束后执行微任务:promise
  3. while 循环时间 2s 大于第一次的 setTimeout,while 执行之后第一次的 setTimeout 已将任务推入事件队列
  4. 第二次的 setTimeout 在 while 之后,故事件队列中宏任务的顺序为 [timeout 1, timeout 2]

参考资料