为了账号安全,请及时绑定邮箱和手机立即绑定

浏览器页面渲染机制 ,了解一下?

从输入 URL 到页面加载完成,发生了什么?

对于前端开发,这个过程非常的重要。了解这个过程,对于前端问题的排查和性能优化提升有很大的作用,其实这个问题——无须你调动太多计算机的专业知识,
只需要你用最快的速度在脑海中架构起这个抽象的过程,对于这个抽象概念的了解将会有意想不到的帮助。

我们现在一起简单地复习一遍这个经典的过程:

首先我们需要通过 DNS(域名解析系统)将 URL 解析为对应的 IP 地址,然后与这个 IP 地址确定的那台服务器建立起 TCP 网络连接,随后我们向服务端抛出我们的 HTTP 请求,服务端处理完我们的请求之后,把目标数据放在 HTTP 响应里返回给客户端,拿到响应数据的浏览器就可以开始走一个渲染的流程。渲染完毕,页面便呈现给了用户,并时刻等待响应用户的操作(如下图所示)。

imageView2

我们将这个过程切分为如下的过程片段:

DNS 解析
   |
TCP 连接
   |
HTTP 请求抛出
   |
服务端处理请求,HTTP 响应返回
   |
浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户

浏览器页面渲染机制

浏览器渲染的处理就是最后一步,浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户

首先我们知道,数据在网络传输中是以“数据包”的形式通过互联网发送,而数据包以字节为单位。

当你编写一些 HTML、CSS 和 JS,并试图在浏览器中打开 HTML 文件时,浏览器会从你的硬盘(或网络)中读取 HTML 的原始字节

浏览器读取的是原始数据字节,而不是你编写的代码的实际字符。 从以上知道。浏览器接收字节数据,但是,它用这些数据什么都做不了。数据的原始字节必须转换为它所理解的形式,这是第一步。

浏览器对象需要处理的是文档对象模型(DOM)对象。那么,DOM 对象是从何而来的呢?这很简单。首先,将原始数据字节转换为字符。

这一点,你可以通过你所编写的代码的字符看到。这种转换是基于 HTML 文件的字符编码完成的。至此,浏览器已经从原始数据字节转换为文件中的实际字符。但这不是最终的结果。这些字符会被进一步解析为一些称为“标记(token)”的东西。

那么,这些标记是什么?文本文件中的一堆字符对浏览器引擎而言没什么用处。如果没有这个标记化过程,那么这一堆堆字符只会生成一系列毫无意义的文本,即 HTML 代码——不会生成一个真正的网站。

当你保存一个扩展名为.html 的文件时,就向浏览器引擎发出了把文件解析为 HTML 文档的信号。浏览器“解释”这个文件的方式是首先解析它。在解析过程中,特别是在标记化过程中,浏览器会解析 HTML 文件中的每个开始和结束“标签(tag)”。解析器可以识别尖括号中的每个字符串,如“< html>”、“< p>”,也可以推断出适用于其中任何一个字符串的规则集。例如,表示锚标签的标记与表示段落标签的标记具有不同的属性。

从概念上讲,你可以将标记看作某种数据结构,它包含关于某个 HTML 标签的信息。本质上,HTML 文件会被分解成称为标记的小的解析单元。浏览器就是这样开始识别你所编写的内容的。

但标记还不是最终的结果。标记化完成后,接下来,标记将被转换为节点。你可以将节点看作是具有特定属性的不同对象。实际上,更好的解释是,将节点看作是文档对象树中的独立实体。但节点仍然不是最终结果。

现在,让我们看一下最后一点。在创建好之后,这些节点将被链接到称为 DOM 的树数据结构中。DOM 建立起了父子关系、相邻兄弟关系等。在这个 DOM 对象中,每个节点之间都建立了关系。现在,这是我们可以使用的东西了。

如果你还记得 Web design 101,你不会在浏览器中打开 CSS 或 JS 文件来查看网页。你打开的是 HTML 文件,大多数情况下是 index.html 这种方式。

你之所以会这样做,是因为浏览器会首先将 HTML 数据的原始字节转换为 DOM。

根据 HTML 文件的大小,DOM 的构建过程可能需要一些时间。无论文件多小,它都需要一些时间。

现在已经从标记转换为节点,又转换成了 DOM。

CSS 如何转换?

DOM 已经创建。带有一些 CSS 的典型 HTML 文件会包含下面这样的样式表链接:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" type="text/css" media="screen" href="main.css" />
</head>
<body>

</body>
</html>

当浏览器接收到原始数据字节并启动 DOM 构建过程时,它还会发出请求来获取链接的 main.css 样式表。当浏览器开始解析 HTML 时,在找到 css 文件的链接标签的同时,它会发出请求来获取它。可能你已经猜到,浏览器还是接收 CSS 数据的原始字节,从互联网或是本地磁盘。

但是,浏览器如何处理这些 CSS 数据的原始字节呢?

从 CSS 的原始字节到 CSSOM

当浏览器接收到 CSS 的原始字节时,会启动一个和处理 HTML 原始字节类似的过程。就是说,原始数据字节被转换成字符,然后标记,然后形成节点,最后形成树结构。

什么是树结构?大多数人都知道 DOM 这个词。同样,也有一种 CSS 树结构,称为 CSS 对象模型,简称为 CSSOM。

你知道,浏览器不能使用 HTML 或 CSS 的原始字节。必须将其转换成它能识别的形式,也就是这些树形结构。

同样的 CSS 字节处理过程!

CSS 有一个叫做级联的东西。级联是浏览器确定如何在元素上应用样式的机制。

由于影响元素的样式可能来自父元素,即通过继承,或者已经在元素本身设置,所以 CSSOM 树结构变得很重要。为什么?这是因为浏览器必须递归遍历 CSS 树结构并确定影响特定元素的样式。

一切顺利。浏览器有了 DOM 和 CSSOM 对象。现在,我们能在屏幕上呈现一些东西了吗?

渲染树

我们现在得到的是两个独立的树结构,它们似乎没有共同的目标。

DOM 和 CSSOM 是独立的树结构

DOM 和 CSSOM 树结构是两个独立的树结构。DOM 中包含关于页面 HTML 元素关系的所有信息,而 CSSOM 则包含关于元素样式的信息。

好了,浏览器现在把 DOM 和 CSSOM 树组合成一棵渲染树。

DOM + CSSOM = 渲染树

渲染树包含页面上所有关于可见 DOM 内容的信息以及不同节点所需的所有 CSSOM 信息。注意,如果一个元素被 CSS 隐藏,例如使用 display; none,那么节点就不会包含在渲染树中。隐藏元素会出现在 DOM 中,但不会出现在渲染树中。这是因为渲染树结合了来自 DOM 和 CSSOM 的信息,所以它知道不能把隐藏元素包含在树中。

构建好渲染树之后,浏览器将继续下个步骤:布局!

现在,我们有了屏幕上的内容和所有可见内容的样式信息——但是我们并没有实际在屏幕上渲染任何内容。

首先,浏览器必须计算页面上每个对象的确切大小和位置。这就好比是,把要在页面上渲染的所有元素的内容和样式信息传递给一个有才华的数学家。然后,这位数学家用浏览器的视窗计算出每个元素的确切位置和大小。

布局进行中

这个布局步骤考虑了从 DOM 和 CSSOM 接收到的内容和样式,并执行了所有必要的布局计算。有时,你会听到人们把这个“布局”阶段称为“回流(reflow)”。

艺术家出场

现在,每个元素的确切位置已经计算出来,剩下的就是将元素“绘制”到屏幕上。

考虑一下。我们已经得到了在屏幕上显示元素所需的所有信息。我们只要把它展示给用户。这就是这个阶段的全部工作。有了元素内容(DOM)、样式(CSSOM)和计算得出的元素的精确布局信息,浏览器现在就可以将节点逐个“绘制”到屏幕上了。元素现在终于呈现在屏幕上了!

渲染阻塞资源

当你听到“渲染阻塞(render-blocking)”时,你会想到什么?我猜你想的是,“有东西阻止了屏幕上节点的实际绘制”。如果你这么说,那你说的完全正确!

优化网站的第一准则是让最重要的 HTML 和 CSS 尽可能快地传递到客户端。在成功绘制之前,必须构造 DOM 和 CSSOM,因此,HTML 和 CSS 都是渲染阻塞资源。关键是,你应该尽快将 HTML 和 CSS 提供给客户端,以优化应用程序的首次渲染时间。

JavaScript 如何执行?

一个好的 Web 应用程序肯定会使用一些 JavaScript。这是一定的。JavaScript 的“问题”在于你可以使用 JavaScript 修改页面的内容和样式。通过这种方式,你可以从 DOM 树中删除元素和添加元素,还可以通过 JavaScript 修改元素的 CSSOM 属性。

这很棒!但是要付出代价的。考虑下面 HTML 文档:

<!DOCTYPE html>
<html>

<head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Medium Article Demo</title>
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <p id="header">How Browser Rendering Works</p>
    <div><img src="https://i.imgur.com/jDq3k3r.jpg"></div>
</body>

</html>

这是一个非常简单的文档。样式表 style.css 只有下面一个声明语句:

body {
  background: #8cacea;
}

在脚本标签中,我将访问 id 为 header 的 DOM 节点,然后将其输出到控制台。可以正常运行,如下所示:

DOM 操作成功

但是,你是否注意到,这个脚本标签位于 body 标签的底部?让我们把它放在 head 中,看看会发生什么:

<!DOCTYPE html>
<html>

<head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Medium Article Demo</title>
    <link rel="stylesheet" href="style.css">
    <script>
        let header = document.getElementById("header");

        console.log("header is: ", header);
    </script>
</head>

<body>

    <p id="header">How Browser Rendering Works</p>
    <div><img src="https://i.imgur.com/jDq3k3r.jpg"></div>
</body>

</html>

一旦我这样做,header 就解析为 null。

DOM 操作失败

为什么会这样?很简单。当 HTML 解析器正在构建 DOM 时,发现了一个脚本标签。此时,body 标签及其所有内容还没有被解析。DOM 构造将停止,直到脚本执行完成:

DOM 构造停止

当脚本试图访问一个 id 为 header 的 DOM 节点时,由于 DOM 还没有完成对文档的解析,所以它还不存在。这把我们带到了另一个重要的问题。脚本的位置很重要。

这还不是全部。如果你将内联脚本提取到外部本地文件 app.js 中,行为是一样的。DOM 的构建仍然会停止:

<!DOCTYPE html>
<html>

<head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Medium Article Demo</title>
    <link rel="stylesheet" href="style.css">
    <script src="app.js"></script>
</head>

<body>

    <p id="header">How Browser Rendering Works</p>
    <div><img src="https://i.imgur.com/jDq3k3r.jpg"></div>
</body>

</html>

那么,如果 app.js 不是本地的而必须通过互联网获取呢?

<!DOCTYPE html>
<html>

<head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Medium Article Demo</title>
    <link rel="stylesheet" href="style.css">
    <script src="https://some-link-to-app.js"></script>
</head>

<body>

    <p id="header">How Browser Rendering Works</p>
    <div><img src="https://i.imgur.com/jDq3k3r.jpg"></div>
</body>
</html>

如果网速很慢,需要数千毫秒来获取 app.js,DOM 的构建也会暂停几千毫秒!!!这是一个很大的性能问题,而且还不止于此。JavaScript 还可以访问 CSSOM 并对其进行修改。例如,这是有效的 JavaScript 语句:

document.getElementsByTagName("body")[0].style.backgroundColor = "red";

那么,当解析器遇到一个脚本标签而 CSSOM 还没有准备好时,会发生什么情况呢?答案很简单。Javascript 执行将会停止,直到 CSSOM 就绪。

因此,虽然 DOM 构造在遇到脚本标签时会停止,但 CSSOM 不会发生这种情况。对于 CSSOM,JS 执行会等待。没有 CSSOM,就没有 JS 执行。

async 属性

在默认情况下,每个脚本都是一个解析器阻断器!DOM 的构建总是会被打断。不过,有一种方法可以改变这种默认行为。如果将 async 关键字添加到脚本标签中,那么 DOM 构造就不会停止。DOM 构造将继续,脚本将在下载完成并准备就绪后执行。

这里有一个例子:

<!DOCTYPE html>
<html>

<head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Medium Article Demo</title>
    <link rel="stylesheet" href="style.css">
    <script src="https://some-link-to-app.js" async></script>
</head>

<body>

    <p id="header">How Browser Rendering Works</p>
    <div><img src="https://i.imgur.com/jDq3k3r.jpg"></div>
</body>

</html>

关键渲染路径

目前为止,我们讨论了从接收 HTML、CSS 和 JS 字节到将它们转换为屏幕上的像素之间的所有步骤。这整个过程称为关键渲染路径。优化网站性能就是优化关键渲染路径。

一个经过良好优化的站点应该能够渐进式渲染,而不是让整个过程受阻。

这是 Web 应用程序慢或快的区别所在。周密的关键渲染路径(CRP)优化策略使浏览器能够通过确定优先加载的资源以及资源加载的顺序来尽可能快地加载页面。

谷歌 Web 基础文档中有关性能的章节

点击查看更多内容
5人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Web前端工程师
手记
粉丝
846
获赞与收藏
196

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消