5. 浏览器是如何工作的?

313 阅读4分钟

浏览器大体由七部分组成:

  • 用户界面:包括地址栏、前后退按钮、书签菜单等;除了主窗口用以显示请求的界面外,其余都属于用户界面;
  • 浏览器引擎:在用户界面和渲染引擎间传递指令;
  • 呈现引擎:负责将请求来的数据呈现出来;
  • 网络:用于网络请求;
  • JS 解释器:用于解析和执行 JS 代码;
  • UI 后端:用以绘制一些窗口小组件,比如弹窗等;
  • 数据存储:存储一些浏览器所需数据,比如 cookie;

这其中,最重要的是呈现引擎,它负责解析传过来的 HTML 和 CSS 文件,并将其转化成可视化的界面。

呈现引擎的大体工作过程如下:

  1. 解析 HTML 和 CSS 文件,分别生成 DOM 树 和 CSSOM 树;
  2. DOM 树 和 CSSOM 树合并成为 Render 树;
  3. 布局;
  4. 绘制(渲染);

在从 1 到 2 的过程中,还穿插着 JS 脚本的执行,有一些 incline css 也需要汇到 CSSOM 上:

** HTML 解析**

  1. 先将 HTML 代码 token 化;
  2. 建构解析树;
  3. 将解析树转化为 DOM 树;

在构建 DOM 树时,如果遇到脚本执行,DOM 树就要暂停构建,执行脚本,执行完毕后重新 token 化。为了防止这种情况出现,一般将 script 放在最后或者用 defer 或异步的方式执行。

这里解释一下什么叫 token 化,其实就是将 html 以一种语义化的方式表达出来,这样浏览器才知道怎么建构解析树;

当 HTML 被语义化后,解析树就出来了:

继而再根据解析树构建 DOM 树:

CSS 解析

解析器会将 CSS 文件解析成 StyleSheet 对象,每个对象对应一个 CSS 规则。CSS 规则包含选择器和声明对象。

Render 树

Render 树,就是以 DOM 为主体,把 CSS 里的规则加到 DOM 树上(这里涉及到同一节点样式的计算,内联样式,外部 CSS 文件,浏览器默认规则等等)。

有一些隐性的属性,比如 headscript ,title 或是 CSS 中设置了隐藏的元素不显示。

下图是 DOM 树(左)和 Render 树(右)的对比:

这里要说明的是,浏览器渲染不是按照 Render 树来的,为了方便处理,它会把 Render 树转化成 Layer 树。

Render 树上的 node 又称为 RenderObject,根据某些规则(是否有页面根节点等,细节不纠),一些 RenderObject 会生成对应的 RenderLayer,而没有被选中的,则从属于父类的 RenderLayer。

说白了,浏览器不是一个平面,它是一个立体面,得先决定哪些先画,哪些后画。

Layer 决定了页面的层次结构,RenderObject 决定了我们看到的内容。

布局

布局是一个递归的过程,要计算出每个节点在屏幕上的实际位置和尺寸,从父节点开始,到子节点。

布局的元素是以盒模型呈现,一般按从上到下,从左到右的方式进行。

布局的过程中会出现重排的问题,比如字体样式改变或屏幕大小改变,那就要重新布局。对于一些微小的变化,一采用 dirty 位的方式,需要改变的元素将自己标注为 dirty,布局时只需要重新计算该区域的信息即可。

绘制

绘制工作是 UI backend 做的,一般的绘制顺序如下:

  1. 背景颜色;
  2. 背景图片;
  3. 边框;
  4. 子代;
  5. 轮廓;

重绘时,浏览器会将原来的页面保存起来,只绘制两者间有差异的地方,而不会重绘整个页面。

这还没完,实际上,在 Render 树 变成 Layer 树时,还有另一进程发生了,有一些Layer 会变成 GraphicsLayer,交给 GPU 去处理(之前的都是 CPU)。好处是 GPU 更擅长处理某些图形绘制工作,效率更高。

这样,在绘制过后,其实还有一个合并的过程,将 CPU 和 GPU 处理的图片合到一起。

所以,一个完整的浏览器处理结构图就出来了:


参考:

  1. So How Does the Browser Actually Render a Website
  2. How Browsers Work: Behind the scenes of modern web browsers
  3. 浏览器的渲染过程
  4. 浏览器的渲染过程2.0 — Composite