导航流程 | 从输入 URL 到页面展示,浏览器都做了什么?

197 阅读6分钟

流程概览

整个过程需要各个继承之间的配合,所以在流程开始之前,我们快速回顾下浏览器进程、渲染进程和网络进程的主要职责。

  • 浏览器进程主要负责用户交互、子进程管理和文件储存等功能
  • 网络进程是面向渲染进程和浏览器进程等提供网络下载功能
  • 渲染进程的主要职责是从网络下载的 HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面。

主流程可概括为以下几步:

  1. 浏览器进程接收到用户输入的 URL 请求,并将该 URL 转发给网络进程
  2. 网络进程发起真正的 URL 请求
  3. 网络进程接收到了响应头数据,便解析响应头数据,并将数据转发给浏览器进程
  4. 浏览器进程接收到网络进程的响应头数据之后,发送“提交导航”消息到渲染进程
  5. 渲染进程接收到“提交导航”消息后,便开始准备接收 HTML 数据,接收数据的方式是直接和网络进程建立数据管道
  6. 最后渲染进程会向浏览器进程“提交文档”,让浏览器进程进入接受和解析页面数据的状态。
  7. 浏览器进程接收到渲染进程“提交文档”的消息后,便开始移除之前旧的文档,然后更新浏览器进程中的页面状态。

渲染流程

渲染流程做了几件最重要的事情,分别是:

  • 将 HTML 文档处理成 DOM 树
  • 将 CSS 文档、内联 CSS、link 的外部 css 文档处理成 styleSheets
  • 将以上步骤的产物合并成最终的展示网页。

渲染流程按时间顺序可分为以下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。

构建 DOM 树

构建 DOM 树的输入内容是 HTML 文件,经由 HTML 解析器解析,最终输出树状结构的 DOM。

为什么要做这一步转化,因为浏览器无法理解 HTML 文件,只能理解 DOM 树结构。

样式计算

样式计算的目的是计算出 DOM 节点中每个元素的具体样式,这个阶段大体可分为三步。

  1. 将 CSS 文档、内联 CSS、link 的外部 css 文档处理成 styleSheets,浏览器只能理解 styleSheets 结构。该结构同时具备了查询和修改功能。
  2. 标准化样式表中的属性值,例如将color:white转化成color:#fff
  3. 根据 CSS 的继承规则和层叠规则,计算出 DOM 树中每个节点的具体样式。

布局阶段

我们现在拥有 DOM 树和 DOM 树中元素的样式,但我们还需要计算出 DOM 树中可见元素的几何位置,才能将页面完整的显示出来。在布局阶段我们需要完成两个任务:

  1. 通过遍历 DOM 树中的所有节点,忽略不可见的节点,筛选出所有可见的节点,把这些节点加到布局树中。
  2. 通过布局计算计算出布局阶段的最终结果,也就是 DOM 树中可见元素的几何位置。

分层

页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-index 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。

明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等,都拥有层叠上下文属性。

{
    pisition: fixed;
    z-index: 2;
    filter: blue(5px);
    opacity: 0.5;
}

当元素或者元素树设置了宽高,并且元素内容超过了宽或者高,那么这个元素就需要被剪裁,或者需要添加滚动条。那么这个元素也会被赋予层叠上下文属性。

图层绘制

在完成图层树的构建后,渲染引擎会把图层树中每个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表。在图层绘制阶段,输出的内容就是这些待绘制列表。

栅格化操作

绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。

合成线程会将每个图层划分成多个图块,再把每个图块转换为位图,这一转换过程会使用 GPU 来加速。最终产物就是许许多多的位图,这些位图按照顺序组成起来才能成为一个完整的页面(也就是渲染后的图层)。

合成和显示

一旦所有图块都转换为位图后,合成线程就会生成一个绘制图块的命令,并将该命令提交给浏览器进程。

浏览器进程接收到命令后,根据命令内容将页面内容绘制到内存中,最后将内存显示在屏幕上。

重排、重绘与合成

  1. 重排是由什么引起的,会触发什么流程?

通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。

这个过程需要触发几乎完整的渲染流程【从计算 styleSheets 到合成和显示】,所以重排导致的开销是最大的。

  1. 重绘是由什么引起的,会触发什么流程?

通过 JavaScript 或者 CSS 修改元素的颜色属性等,那么浏览器将跳过布局和分层阶段,所以执行效率会比重排操作要高一些。

  1. 直接合成阶段

通过 CSS 的 transform 来实现动画效果,那么浏览器将会避开重排和重绘阶段,直接在非主线程上执行合成动画操作,所以相对于重绘和重排,合成能大大提升绘制效率。

减少重排重绘的方法

  1. 使用 class 操作样式,而不是频繁操作 style
  2. 避免使用 table 布局
  3. 批量 dom 操作,例如 createDocumentFragment,或者使用框架,例如 React
  4. Debounce window resize 事件
  5. 对 dom 属性的读写要分离
  6. will-change: transform 做优化