关键渲染路径

  • 浏览器会 “偷看” DOM, 预先下载相关外联资源(CSS, JS)
  • 当 HTML 解析器遇到一个 Script 标记时, 它会暂停构建 DOM, 将控制权移交给 JS 引擎. 等 JS 引擎运行完毕, 浏览器会从中断的地方恢复 DOM 构建
    • 内联和同步 JS 在何处插入, 就在何处执行
  • CSSOM 构建不会阻塞 DOM, 但会因为阻塞后续的 JS (因为后续 JS 有可能需要查询或者计算任何对象的 Style) 阻塞 DOM 从而阻塞 domContentLoaded
  • 为达到更好的用户体验, 浏览器可以在不断接收和处理来自网络的其余内容的同时将部分内容解析并显示出来
    • Chrome 69开始, Body 内的 CSS 已经不再阻塞整个页面的渲染, 这里 有 Jake Archibald 的详细解释(其实 Firefox 很早就支持了)
    • 不是每次 DOM 构建后都需要 paint (如果啥都没变为啥要呢…)
    • 有可能在 DOMContentLoaded 前先 paint 过部分 DOM
    • 当渲染时间远小于 JS 下载时间, 且所有 JS 都下载完了, 浏览器会选择先 paint 出已经 parse 的部分
      • 机器性能好的时候(pc)
      • JS 下载很慢的时候(不在本地)

image.png

  • DOM 树构建: DevTools 里的 Parse HTML
  • CSSOM 树构建: DevTools 里的 Recalculate Style 表示捕获解析和 CSSOM 树构建以及样式计算
  • DOM 树与 CSSOM 树合并后形成渲染树(Render Tree): DevTools 里的 Layout 表示捕获渲染树构建以及位置和尺寸计算
    • 不可见的节点不会出现在渲染树中

Script 的 async 和 defer 属性

  • 可以在这里找到相关的标准
  • document.createElement 添加的 Script 默认是 async 的
  • 使用 async 标签可以使 JS 在下载过程中不阻塞 DOM 构建
  • 使用 defer 可以在 async 的基础上即使 JS 下载完成了也不阻塞 DOM 构建
  • 多个 defer 脚本会需要按照在页面中的顺序执行
  • 多个 async 脚本按照下载完成顺序执行
  • domInteractive 代表 DOM 树构建完成, 而 domContentLoaded 则需要把同步, defer 的脚本都执行完才会触发. 标准里指出了他们的区别, 因此
    • 使用 defer 标签不会阻塞 domInteractive, 但会阻塞 domContentLoaded
    • 使用 async 标签除非下载时间特别短才会阻塞 domInteractivedomContentLoaded(但是时间已经这么短了, 大概率也不会有什么影响了吧…)

阻塞路径

graph TD
E(("end"))
B(("begin"))
typescript{CSS后script种类}
hasjsCSSOM[CSSOM构建]
nojsCSSOM[CSSOM构建]

B -.无Script.-> nojsCSSOM
B -.有Script.-> hasjsCSSOM
B -.DOM构建.-> domInteractive
nojsCSSOM --> 渲染树构建
hasjsCSSOM --> typescript
typescript --> defer
typescript --> sync/inline
typescript --> async
defer --> DOMContentLoaded
sync/inline --> domInteractive
async -.下载速度比parseHTML快.-> domInteractive
async -.下载速度比parseHTML慢.-> E
domInteractive --> DOMContentLoaded
DOMContentLoaded --> 渲染树构建
渲染树构建 --> layout
layout --> paint
paint --> composite
composite --> E

像素管道(pixel pipeline)

当用浏览器绘制的过程由以下5个步骤组成(如果是第一次加载的话当然还需要 Parse HTML)

  • Javascript: JS 操作
  • Style: 样式计算
  • Layout: 布局, 排版. 在Firefox 中叫 Reflow
  • Paint 绘制
  • Composite 合成: 将页面的已绘制部分放在一起以在屏幕上显示的过程

image.png

触发规律

我们可以在 CSS Triggers 查询修改哪些属性会触发 Paint, Layout 和 Compoiste(从表上来看 Firefox 很牛逼啊). 这里简要的罗列了那些方法会导致重排. 总结下来有一下几点

  • 通常对几何属性(width, height, left, top…) 的更改会触发 Layout
  • 除 transform 或 opacity 属性之外, 更改任何属性始终都会触发 Paint
  • 有的时候浏览器会收集 reflow 操作到缓存队列中直到一定的规模或者过了特定的时间, 再一次性地 flush 队列. 因此当读取布局信息时 (如getComputedStyle())有时也会导致 reflow

Chrome 进程模型

Multi-process Architecture

  • 一般来说, Chrome 里进程分两类
    • 主进程(browser process)
    • 每个 tab 或者扩展为独立进程(renderer processes), 可以在 Activity Monitor 里看到一个 Google Chrome Helper
  • 线程
    • GUI
      • GUI渲染线程负责渲染浏览器界面(repaint, reflow), 与JavaScript引擎互斥??, 当JavaScript引擎执行时GUI线程会被挂起, GUI 更新会被保存在一个队列中(Command Buffer)等到 JavaScript 引擎空闲时立即被执行
    • JS 引擎
    • Web Worker
    • Script Streaming
    • 定时器(负责将 setTimeout 里面的语句塞到 queue 里去的任务也是在单独的线程里执行的(里面的语句还是要在 js 引擎所在的线程执行的)
    • 事件触发
    • 网络请求(貌似是主进程里的线程)

  • 浏览器会将 相同的请求,依次发出(前一个请求响应后,再发出下一个请求)

Deciphering the Critical Rendering Path(关键渲染路径) by Ilya Grigorik 浏览器的工作原理 by Tali Garsiel and Paul Irish LIFE OF A pixel by Steve Kobes: video / slide 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理 by dailc