JavaScript 运行时
在执行 JavaScript 代码的时候,JavaScript 运行时实际上保护了一组用于执行 JavaScript 代码的 代理。每个代理由一组执行上下文的汇合、执行上下文栈、主线程、一组可能创立用于执行 worker 的额定的线程汇合、一个工作队列以及一个微工作队列形成。除了主线程(某些浏览器在多个代理之间共享的主线程)之外,其它组成部分对该代理都是惟一的
事件循环(Event Loops)
每个代理都是由事件循环驱动的,事件循环负责收集用事件(包含用户事件以及其余非用户事件等)、对工作进行排队以便在适合的时候执行回调。而后它执行所有处于期待中的 JavaScript 工作(宏工作),而后是微工作,而后在开始下一次循环之前执行一些必要的渲染和绘制操作。(本节文字来自 mdn,集体感觉形容有误,具体程序往下看)
Event Loops 中,每一次循环成为 tick,每次 tick 的流程如下:
- 执行主线程中的同步代码(能够看做一个宏工作),直到执行实现
- 查看微工作队列,若不为空,则执行微工作队列中的所有微工作,直到队列为空
- 查看宏工作队列中在以后 tick 之前退出到队列中的宏工作,顺次执行
- 没执行完一个宏工作,反复顺次第二个步骤
- 当微工作队列已清空且宏工作队列中以后 tick 之前退出的宏工作都已执行实现,执行一些别要的渲染和绘制工作(宿主环境为浏览器时)
- 完结以后 tick,进入下一个 tick
分类
- Window 事件循环
- Worker 事件循环
- Worklet 事件循环
宏工作 & 微工作
一个工作(宏工作 )就是指打算由规范机制来执行的任何 JavaScript,如程序的初始化、事件触发的回调等。除了应用事件,你还能够应用 setTimeout() 或者 setInterval() 来增加工作。
起初微工作和工作之间的差别看起来不大。它们很类似;都由位于某个队列的 JavaScript 代码组成并在适合的时候运行。
宏工作和微工作的区别
- 当执行来自工作队列中的工作时,在每一次新的事件循环开始迭代的时候运行时都会执行队列中的每个工作。在每次迭代开始之后退出到队列中的工作须要在下一次迭代开始之后才会被执行.
- 每次当一个工作退出且执行上下文为空的时候,微工作队列中的每一个微工作会顺次被执行。不同的是它会等到微工作队列为空才会进行执行——即便中途有微工作退出。换句话说,微工作能够增加新的微工作到队列中,并在下一个工作开始执行之前且以后事件循环完结之前执行完所有的微工作。
创立路径
宏工作:
- 程序的初始化
- 事件触发的回调
- setTimeout()/setInterval()
- postMessage, MessageChannel
- setImmediate() node 环境
微工作:
- Promise
- MutaionObserver
- Object.observe(已废除,被 Proxy 对象代替)
- process.nextTick() node 环境
- queueMicrotask()
代码示例
setTimeout(() => {console.log(3);
// 在执行宏工作时退出宏工作,退出的宏工作会在下个 tick 执行
setTimeout(() => {console.log(8);
});
// 在执行宏工作时退出微工作,以后宏工作执行实现后,立刻执行该微工作
new Promise(resolve => {resolve();
console.log(4);
}).then(() => {console.log(6);
});
// 在执行宏工作时退出宏工作,退出的宏工作会在下个 tick 执行
setTimeout(() => {console.log(9);
});
console.log(5);
});
setTimeout(() => {console.log(7);
});
new Promise(resolve => {resolve();
console.log(1);
}).then(() => {console.log(2);
});
// 输入后果:// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
参考文档:
- 在 JavaScript 中通过 queueMicrotask() 应用微工作
- 深刻:微工作与 Javascript 运行时环境
- 宏工作和微工作到底是什么?