运行时
概念
进程
- 进程是 CPU资源分配的最小单位
- 进程之间相互独立
- 主进程可以拥有子进程
- 进程可以被多个线程共同拆分运行
线程
- 线程是 CPU调度的最小单位
- 线程相互独立又可以共享当前进程的内存
- 线程共同运行来执行进程
- 浏览器和nodejs均为多线程,js可以通过web worker实现多线程
宏任务
- 事件
- 可以理解为不是js引擎去执行的内容
- 宏任务执行完成后将数据交到宏任务队列
微任务
- 宏任务执行后的回调代码的消息队列
- 该队列将全部出队进入执行栈执行后,开启下一个宏任务
运行
- FIFO
- 主代码运行栈(异步交予其他线程处理,处理完的交到消息队列,清空后执行事件循环的消息队列)
- 事件循环(按照消息队列重复执行宏任务和微任务)
V8引擎
角色
- Stack(Frame) 执行栈
- Queue(Message) 消息队列
- Heap(Object) 堆
顺序
- 执行后,会进行入栈,先入后出
- 执行栈清空后消息队列中开始出队
事件循环
- 宏任务队列 - 出队 -> 宏任务 -> 微任务队列 - 清空 -> ... -> 宏任务队列清空
浏览器
- GUI渲染线程
- 执行js线程,该线程阻塞GUI线程
- 定时器线程
- 事件触发线程
- 异步请求线程
Macro-Task
- 异步任务的主体
- setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染等
Micro-Task
- 异步任务执行后的任务
- new Promise().then(回调)、MutationObserver(html5新特性) 等
- 回调函数中的代码
执行
- 先执行执行栈,执行栈清空后执行宏任务出队后的任务
- 可以有多个宏任务队列,但只有一个微任务队列
- 宏任务一个个执行,微任务一队一队执行
- 执行完宏任务后有微任务,则执行整队微任务,微任务队列清空后,然后在执行队列中的下一个宏任务
nodejs(11版本及之后版本)
- nodejs运用V8引擎解析js,使用libuv处理i/o
- 微任务与宏任务和浏览器基本一致
- 执行栈清空后,先执行宏任务出队,清空微任务队列,再执行出队
- process.nextTick在当前代码执行完,事件循环执行前执行
宏任务
- 定时器:本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数。
- 待定回调:执行延迟到下一个循环迭代的 I/O 回调。
- idle, prepare:仅系统内部使用。
- 轮询:检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。
- 检测:setImmediate() 回调函数在这里执行。
- 关闭的回调函数:一些关闭的回调函数,如:socket.on('close', ...)。
回调队列(微队列)
轮询队列
- 包含于宏任务队列
- 轮询阶段
- 计算应该阻塞和轮询 I/O 的时间。
- 然后,处理轮询队列里的事件
- 当轮询队列不为空时,执行队列
- 当轮询队列为空
- 如果后续有setimmediate调度,进入check阶段
- 没有,则进入等待状态
示例
const net = require('net');
console.info('start');
const socket = net.createServer().listen(3000);
setTimeout(() => {
console.info('timer1');
}, 0);
Promise.resolve().then(() => {
socket.on('close', () => {
console.info('closeCallback');
});
socket.close();
setTimeout(() => {
console.info('timer2');
}, 0);
setImmediate(() => {
console.info('timer_imm');
});
});
console.info('end');
特别说明
参考