Event loop
- macrotask
- setTimeout
- setInterval
- setImmediate(仅 IE10 以上支持)
- requestAnimationFrame(浏览器独占)
- microtask
- process.nextTick(Node 独占)
- MutationObserver(浏览器独占)
- Promise.then
- catch、finally
- 过程:
- 同步代码开始执行
- 发现异步代码,同步代码继续向下执行,异步代码挂到异步线程执行,执行完毕后添加回调到 macrotask/microtask 队列
- 当前 loop 的同步代码执行完毕
- 清空 microtask 队列
- 从 macrotask 队列取最前方的 task 执行
- microtask 和 macrotask 的执行方式依旧遵循 1~5
浏览器的进程与线程
多进程
- Browser 进程:主进程,负责协调控制其他进程
- 第三方插件进程:如 AdblockPlus,仅当使用插件时才会创建
- GPU 进程:最多一个,用于 3D 绘制等。
- Renderer 进程:每个 tab 对应一个 Renderer 进程,多个空标签页会合并到同一个进程里,每个 Renderer 进程都是多线程的。
Renderer 进程
- GUI 渲染线程
- JS 引擎线程
- 事件触发线程:控制 Event loop,将异步事件执行完毕后的回调推送到对应的队列中等待 JS 引擎线程执行。
- 定时器触发线程
- 异步 http 请求线程
页面渲染
- 处理 HTML 标记并构造 DOM 树
- 处理 CSS 并构建 CSSOM 树
- 将 DOM 和 CSSOM 组合成一个 Render 树,计算样式树或渲染树从 DOM 树的根开始构建,遍历每个可见节点。
- 在渲染树上运行布局以计算每个节点的几何体
- 将各个节点绘制到屏幕上
修改 DOM 的样式、大小和位置会触发 reflow 或 repaint,现代浏览器会尽可能将多次 reflow 或 repaint 操作放到一起一次性执行完,减少资源浪费。
Vue nextTick
代码中用户很可能是多次修改数据的,每次修改都立即通知数据对应的 watcher 并更新 DOM 肯定是打咩,太浪费性能了,所以要把本次 tick 的 DOM 修改放到一起,等待同步代码都完事儿了,再去执行 DOM 修改的操作,注意 DOM 的修改是同步的,Render 才是异步且在 microtask 之后的,所以 DOM 的修改是在本次 tick 的同步代码执行完毕后且队列开始清理前执行的,这也是 nextTick 为什么首选 microtask 的原因,就是为了尽早地让回调在 DOM 修改后执行。