JS 执行机制

notion image

浏览器和 NodeJS 执行机制

浏览器执行机制图
notion image
notion image
notion image
NodeJS 执行机制图
notion image
阶段概述:
💡
每个框被称为事件循环机制的一个阶段, 每个阶段都有一个 FIFO 队列来执行回调
  • 定时器:本阶段执行已经被 setTimeout()setInterval() 的调度回调函数。
    • 待定回调:执行延迟到下一个循环迭代的 I/O 回调。
      • idle, prepare:仅系统内部使用。
        • 轮询:检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。
          • 检测:setImmediate() 回调函数在这里执行。
            • 关闭的回调函数:一些关闭的回调函数,如:socket.on('close', ...)

              macroTaskQueue/microTaskQueue/nextTickQueue 执行顺序

              浏览器环境
              💡
              setImmediate 不是浏览器标准功能, 兼容性不好
              • 宏任务: setTimeout, setInterval, I/O,setImmediate(如果存在, 目前只有 Edge 和 IE11 支持),requestAnimationFrame(存在争议)
                • 微任务: Promises,MutationObserver
                  总结:
                  • 浏览器环境下, 会依次执行 宏任务 -> 微任务栈 -> 宏任务 -> 微任务栈
                    NodeJS 环境
                    💡
                    NodeJS 有主执行线程, I/O线程, worker_threads 线程, 没有定时器线程, 也没有宏任务栈的概念, 但是在 NodeJS 11 的某个版本之后, 主动向浏览器的逻辑靠拢, 导致可以近似理解为 NodeJS 是有宏任务栈的
                    • 阶段任务:setTimeout, setInterval, setImmediate
                      • 微任务:process.nextTick, promise.then
                        总结:
                        • NodeJS 11 的某个版本之前: 在每个 timmerimmediate 执行之后, 并不会执行 nextTick栈 → 微任务栈, 而是会在每个阶段结束前统一清空
                          • NodeJS 11 的某个版本之后: 在每个 timmerimmediate 执行之后, 都会执行 nextTick栈 → 微任务栈, 为了与浏览器事件机制保持一致
                            • 复现代码
                          相关 Issue:
                          1. timers: run nextTicks after each immediate and timer #22842.
                            1. MacroTask and MicroTask execution order #22257.
                              1. implement queueMicrotask #22951.

                                宏任务微任务兼容

                                微任务基本原理

                                💡
                                核心微任务代码实现见 core-jsmicrotask 模块
                                1. 优先使用 queueMicrotask 方法
                                  1. 浏览器可以使用 MutationObserver, 除了 iOS 环境
                                    1. 有些环境实现了非完全正确的 Promise(例如 WebKit ~ iOS Safari 10.1), 可以使用 Promise.then 实现
                                      1. NodeJS 使用 process.nextTick
                                        1. 其他环境使用 macrotask 实现

                                          宏任务基本原理

                                          💡
                                          核心微任务代码实现见 core-jstask 模块
                                          1. 优先使用 setImmediateclearImmediate
                                            1. 早期 NodeJS 0.8 - 没有 immediate, 使用 process.nextTick
                                              1. Sphere (JS game engine) 使用 Dispatch.now
                                                1. 浏览器使用 MessageChannel 除了 iOS 环境
                                                  1. 其他浏览器(除了 IE8 -), 使用 global.postMessageglobal.addEventListener('message', ...)
                                                    1. 其他浏览器使用 html.appendChild(createElement('script'))['onreadystatechange']
                                                      1. 其余的 setTimeout 兜底

                                                        特殊示例

                                                        关于 setTimeout/setInterval 0 延迟
                                                        💡
                                                        The HTML5 standard says: “after five nested timers, the interval is forced to be at least 4 milliseconds.”
                                                        结果说明:
                                                        • 在浏览器中如果 setTimeout 被嵌套了 5 次, 之后的回调间隔至少为 4ms
                                                          • 在 NodeJS 中执行没有这个限制
                                                            原因分析:
                                                            • 如果没有延迟有可能会导致 UI 锁死, CPU 过载
                                                              • 操作系统的 clock 本身也不是很准确
                                                                相关代码:
                                                                setTimeout 示例代码
                                                                setInterval 示例代码
                                                                关于 process.nextTick 和 Promise.then
                                                                process.nextTick 比 Promise.then 先执行
                                                                process.nextTick 注册的回调会在事件循环的当前阶段结束前执行
                                                                关于 Promise.then 和 Promise.resolve 执行顺序
                                                                示例1
                                                                示例2

                                                                Reference


                                                                © Jiyu Shao 2018 - 2025