图解Chrome:HTML/CSS/JS是如何在浏览器中,渲染成你看到的页面?
作者:CQITer小编 时间:2018-10-23 09:08
Chrome 算是程序员的标配了,从全球的市场份额来看,它在全球市场的份额已经超过 60%。
在 Chrome 10 周年之际,官方发布了一个系列文章,用图解的方式,很清晰的讲解了现代浏览器的运行原理。
渲染器进程的内部工作原理
本系列分为 4 个部分,主要讲解关于现代浏览器的运行原理,本文为该系列的第 3 篇。在之前的文章中,我们介绍了多进程架构和导航的完整流程,而在这篇文章中,我们将探究在渲染器进程的内部,到底发生了什么。
渲染器进程涉及到 Web 性能相关的多个方面,由于渲染器进程中处理了很多的逻辑,不是一篇文章可以全面讲解的,因此本文仅作为一个概述。如果你有兴趣深入研究,可以在《Why Performance Matters》这篇文章里找到更多的资料。
渲染器进程处理Web内容
所有选项卡内发生的逻辑,都由渲染器进程负责。在渲染器进程中,主线程处理了服务器发送给用户的大部分代码。如果你使用到 Web Workder 或者Service Worker,那 JavaScript 中的这部分代码,将由工作线程处理。Compositor(合成器) 和 Raster(光栅) 线程也在渲染器内运行,从而实现高效、流畅的渲染页面。

渲染器进程的核心工作是将 HTML,CSS 和 JavaScript 转换为用户可以与之交互的网页。
上图中,描述了具有主线程、工作线程、Compositor 线程、Raster 线程的渲染器进程,以及他们之间的关系。
解析
构建 DOM
当渲染器进程收到一个导航请求,并开始接收 HTML 数据,主线程将开始处理文本字符串(HTML),将其解析成 DOM(Document Object Model)。
DOM 是 Web 页面的内部的逻辑树文档结构,Web 开发人员可以通过 JavaScript 脚本与之交互数据,以及通过标准 API 来操作 DOM 节点。
将 HTML 文档解析成 DOM 是完全依照于 HTML 协议。并且在 HTML 协议中,浏览器不会对错误的 HTML 进行错误提示。例如,缺少结束的 </p> 标签时,这依然是一个有效的 HTML。类似 Hi! <b>I'm <i>Chrome</b>!</i> 中,b 标签在 i 标签之前关闭这样的错误,会被 HTML 理解为 Hi! <b>I'm <i>Chrome</i></b><i>!</i>。这是因为 HTML 规范的主要原则是优雅的处理这些错误,而不是严格检查。
如果你对这些规范感到好奇,可以阅读 HTML 规范中的 “解析器中的错误处理和奇怪案例介绍” 部分。
解析器中的错误处理和奇怪案例介绍:
https://html.spec.whatwg.org/multipage/parsing.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser
子资源加载
一个完整的 Web 站点通常会包含图片、CSS 和 JS 等外部资源,这些文件都需要从网络或者本地缓存中加载。主线程可以在解析构建 DOM 的时候,将他们逐个请求,但是为了加快速度,会同时使用 “预加载扫描(Preload Scanner)”。
如果 “预加载扫描” 发现有类似 <img> 或 <link> 这样的标签时,会由 HTML 解析器对该资源生成一个 Tokens,然后在浏览器进程中,通过网络或者本地缓存来加载资源。

上图描述了,主线程解析 HTML 并构建 DOM 树的过程。
JS 可以阻止解析
当 HTML 解析器遇到 <script> 标签的时候,它会暂停解析 HTML 文档,然后对这个 JS 脚本进行加载、解析和执行。
这么设计的原因,是因为 JS 可以使用类似 document.write() 方法来改变 DOM 的结构。这就是 HTML 解析器在重新解析 HTML 之前,必须等待 JS 脚本执行的原因。
如果你对 JS 执行中发生的事情细节有兴趣,V8 团队有一篇文章深入的对此进行了讲解,有兴趣可以看看。
V8 团队深入研究:
https://mathiasbynens.be/notes/shapes-ics
提示浏览器如何加载资源
HTML 遇到 JS 脚本则暂停对 HTML 的解析,这并不是绝对的。
Web 开发人员可以通过多种方式的配置,告知浏览器如何更优雅的加载资源。如果你的 JS 脚本中,没有使用到类似 document.write() 这样的方法,你可以在 script 标签中添加 async 或 defer 标记,然后浏览器会异步加载和运行此 JS 脚本,不会阻断解析。如果需要,也可以使用 JavaScript Modules,还可以通过 <link rel="preload"> 标签向浏览器明确标记此为重要的资源,将在页面加载完成之后被立刻使用,对于这类资源,它会在页面加载生命周期的早期,被优先加载。
样式渲染(Style)
仅仅解析成 DOM,还不足以完成页面渲染,因为还可以通过在 CSS 中,设置元素的样式来丰富渲染效果。
主线程将解析 CSS,并将效果渲染到指定的 DOM 节点上,关于 CSS 选择器如何定位到指定的 DOM 节点,可以通过 DevTools 来查看相关信息。

上图中,主线程解析 CSS 并添加渲染样式。



