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