从 Event loop 到 Vue nextTick

189 阅读2分钟

Event loop

  • macrotask
    • setTimeout
    • setInterval
    • setImmediate(仅 IE10 以上支持)
    • requestAnimationFrame(浏览器独占)
  • microtask
    • process.nextTick(Node 独占)
    • MutationObserver(浏览器独占)
    • Promise.then
    • catch、finally
  • 过程:
    1. 同步代码开始执行
    2. 发现异步代码,同步代码继续向下执行,异步代码挂到异步线程执行,执行完毕后添加回调到 macrotask/microtask 队列
    3. 当前 loop 的同步代码执行完毕
    4. 清空 microtask 队列
    5. 从 macrotask 队列取最前方的 task 执行
    6. microtask 和 macrotask 的执行方式依旧遵循 1~5

浏览器的进程与线程

多进程

  • Browser 进程:主进程,负责协调控制其他进程
  • 第三方插件进程:如 AdblockPlus,仅当使用插件时才会创建
  • GPU 进程:最多一个,用于 3D 绘制等。
  • Renderer 进程:每个 tab 对应一个 Renderer 进程,多个空标签页会合并到同一个进程里,每个 Renderer 进程都是多线程的。

Renderer 进程

  • GUI 渲染线程
  • JS 引擎线程
  • 事件触发线程:控制 Event loop,将异步事件执行完毕后的回调推送到对应的队列中等待 JS 引擎线程执行。
  • 定时器触发线程
  • 异步 http 请求线程

页面渲染

  1. 处理 HTML 标记并构造 DOM 树
  2. 处理 CSS 并构建 CSSOM 树
  3. 将 DOM 和 CSSOM 组合成一个 Render 树,计算样式树或渲染树从 DOM 树的根开始构建,遍历每个可见节点。
  4. 在渲染树上运行布局以计算每个节点的几何体
  5. 将各个节点绘制到屏幕上

修改 DOM 的样式、大小和位置会触发 reflow 或 repaint,现代浏览器会尽可能将多次 reflow 或 repaint 操作放到一起一次性执行完,减少资源浪费。

Vue nextTick

source code

代码中用户很可能是多次修改数据的,每次修改都立即通知数据对应的 watcher 并更新 DOM 肯定是打咩,太浪费性能了,所以要把本次 tick 的 DOM 修改放到一起,等待同步代码都完事儿了,再去执行 DOM 修改的操作,注意 DOM 的修改是同步的,Render 才是异步且在 microtask 之后的,所以 DOM 的修改是在本次 tick 的同步代码执行完毕后且队列开始清理前执行的,这也是 nextTick 为什么首选 microtask 的原因,就是为了尽早地让回调在 DOM 修改后执行。

References