浏览器的 performance 中多个指标的含义

浏览器的 performance 中多个指标的含义

Parse HTML

蓝色框,解析 HTML 结构,生成 DOM 树。

我们在请求一个 html 页面时,当 Chrome 渲染引擎接收到 HTML 的字节流时候,就会开始解析 HTML。HTML 解析器并不是等整个文档全部获取之后才开始解析,而是加载了"足够"的数据后,就开始解析了。

以 bilibili 页面为例:

请添加图片描述

请添加图片描述

从上面可以看出,这一个 Parse HTML 任务只解析到了 38 行,从侧面印证了解析 HTML 的过程并不是一次全部执行完的。而是分成多个 HTML 部分进行解析。

HTML 的解析将字节流转换成 token,然后把 token 解析成 Dom 节点并添加到 Dom 树中。

HTML 解析生成 DOM 树

在 HTML 解析器工作过程中,会遇到 script、link 标签需要特殊处理:

请添加图片描述

从上面可以看到这个 HTML 部分的解析包含了多个 Parse StyleSheet 和多个 Evaluate Script。其实就是多个 link、script 标签。

我们可以点开细看:

请添加图片描述

请添加图片描述

请添加图片描述

其实就是 html 里的对应行数的部分:

请添加图片描述

从上面几个图可以看到 link、script 标签这些会显著拉长了 Parse HTML 的用时。

资源请求

我们来看一个比较有意思的东西:

请添加图片描述

在上面的图中可以看到,在解析 HTML 之前,就已经发送了资源请求(set request)。为什么会这样呢?不应该是先解析 HTML,才能知道对哪些资源进行发起请求吗?

在 HTML 中引入的 js,存在修改 Dom 的可能,所以浏览器一般在遇到 <script> 标签后,会先暂停 HTML 解析,优先 js 的下载和执行。但是下载是相对耗时的,如果因为下载时间久而卡住了页面解析,很容易导致用户体验变差,因此 Chrome 采用了一些优化策略。具体来说,就是当 Chrome 渲染引擎接收到 HTML 的字节流时候,会开启一个专门用来分析字节流中所包含 js、css 文件的预解析线程。解析到相关信息之后,预解析线程会提前开始下载这些资源文件,这样在需要使用的时候就可以直接执行,避免了下载的等待时间。

请添加图片描述

在 Parse HTML 蓝色方块下方,还有一些 send request,这些怎么就不是提前下载的呢?我的理解是,这些资源其实都是在预解析线程下载的,尽管在时间上会存在重叠,但和主线程不属于同一个线程,所以 performance 工具会这么显示。

send request 的时刻同样与资源加载的优先级有关:

html结构:

<link href="./styl23r2re.css" rel="stylesheet" media="print" onload="this.media='all'" />
<link href="./style.css" rel="stylesheet" />

请添加图片描述

从上图可以看到 style.css 的请求是优先于 styl23r2re.css 的,并不是按照 HTML 的顺序,在这种情况下资源加载的优先级起作用,比如普通的 script 标签引用的资源,普通 link 引用的资源,或是 rel=preload 或 as="style"预加载的资源,可能会被优先处理。而当资源是 prefetch,或者用 <link rel="stylesheet" href="//s1.hdslb.com/bfs/static/jinkela/long/font/regular.css" media="print" onload="this.media='all'"/> 这种方式的,由于优先级低,就会被延后下载。一般的其他资源,则按顺序下载。

我们在 Summary 栏也可以看到优先级:

请添加图片描述

请添加图片描述

Evaluate Script

黄色框,执行 script 脚本。

如果 <script> 标签内包含 js 内容的,那么这个 Evaluate Script 会在 Parse HTML 下面,比如上面展示过的这张图:

请添加图片描述

要是通过 <script> 标签导入的 js 文件,跟上面的就有所不同:

请添加图片描述

上面的图就是通过 <script> 标签导入的 js 文件的情况,可以很明显看到,Parse HTML 部分是位于 Evaluate Script 后的,也就是说明了 HTML 解析器在解析到 <script> 标签时,也是会等到文件加载完后才会继续解析剩下的 HTML。如果这个文件过大或者响应太慢,就会给用户很明显的白屏感。默认的 <script> 标签都是同步加载的。

async、defer

如果我们熟悉 HTML,就会知道在 <script> 中是可以通过配置一些属性将其从同步加载变成异步加载。

  • async: 异步加载对应的 js 脚本,不阻塞 HTML 页面的渲染,当对应的 js 加载完成后,如果此时 HTML 页面还未加载完成,那么会阻塞页面的渲染,等 js 执行完成后再继续 HTML 页面的加载。
  • defer: 异步加载对应的 js 脚本,不阻塞 HTML 页面的渲染,当对应的 js 加载完成后,如果此时 HTML 页面还未加载完成,那么不会阻塞页面的渲染,等 HTML 页面加载完成后再接着执行加载完成的 js 脚本。
  • type=“module”: 也是能起到异步加载的效果,效果同 defer,不过其可以配合 async 属性让 js 加载完成后立即执行。

概念看起来很抽象,那么我们用 performance 更好的理解:

async:

请添加图片描述

在上面的图中可以看到,DOMContentLoad 事件已经触发了,也就是它不会等待 js 加载完,会继续页面的渲染(注意此时 load 事件还是没出发的),等到 js 加载完了,由于页面已经渲染完了,因此就会直接触发 load 事件。

在看一个 async 阻碍渲染的例子:

请添加图片描述

在 js 加载完后执行这个 js,直接中断 HTML 解析,其实看起来就跟同步加载类似。

defer

请添加图片描述

从上图可以看到,render.js 已经加载完了,但是此时 HTML 还没解析完,因此会处于等待阶段,等到 HTML 解析完后才执行 js 内容,之后触发 DOMContentLoad 及 load 事件。

Parse StyleSheet

解析通过 link 标签导入的 css。如果标签内的 rel 属性不是 styleSheet,那么不会触发这个解析,也就是说此时加载的资源是异常的,并不会解析为 css,因此不会有这个阶段。而像内敛 css、style 标签内的 css 也不会触发这个解析阶段,而是直接进入 Recalculate Style。

请添加图片描述

上面是正常解析 link 标签中 rel=“styleSheet” 的情况,在触发 finish Loading 触发之前,就进行了 Prase StyleSheet 操作。

下面的是 link 标签中 rel 为 prefetch 的情况,可以看到在 style.css 加载完后并没有触发 Parse StyleSheet,而是在触发 finish Loading 后就没有额外处理了:
请添加图片描述

在这个阶段中,主要是解析样式表,构建出 CSSOM(css object model 的简写, css 对象模型)。

CSS 对象模型是一组允许用 JavaScript 操纵 CSS 的 API。它很像 DOM,但针对的是 CSS 而不是 HTML。它允许用户动态地读取和修改 CSS 样式。

Recalculate Style

将 HTML 中的 CSS 样式(外联、内联)输出为 styleSheets,在这个过程中会进行一些包括递归(比如想知道父容器的大小就得先知道子元素的大小)的样式计算。

styleSheets 有两个作用:

  • 可以与 DOM 树结合为页面带来样式
  • JS 可以操作 styleSheets 改变页面样式

我们可以从控制台打印document.styleSheets直观感受它的存在:

请添加图片描述

Layout

有了 DOM 树与 styleSheets,接下来需要为视图中可见部分生成一棵树( render tree 是不包含例如 <meta>display: none 这些无需展示的元素)。

注意此时的树可能并不是完整的:

请添加图片描述

可以看到 HTML 直接解析到 179 行,因此 Layout 阶段是生成前 179 行的渲染树(render tree)。

这个阶段给它换个名称可能我们就会感觉很熟悉 —— 回流/重排。

当元素的规模尺寸,布局,隐藏等改变的时候,就会重新触发 Recalculate Style、Layout、pre-paint、paint。

比如下面的图,我们在 html 里添加一个按钮监听器,当点击按钮时会更改 div 标签的 height 值,需要更新 styleSheets 并重新排版渲染:

请添加图片描述

pre-paint

以前这里叫做 update layer tree, 2022年3月份之后改成了 pre-paint。这里其实是遍历 render tree 生成 layer tree 的过程。

那么 render tree 与 layer tree 有什么区别?

render tree 是 Dom 和 cssom 结合的产物,是将计算后的样式添加到了 Dom 节点上。但是目前只是知道了节点是否可见以及可见样式,还不知道节点的精确位置和大小,这时候就需要布局。渲染引擎从 render tree 的根节点开始遍历,通过一定的规则处理后,将会得到一个 layout tree,这个 layout tree 精确的描述了每个视口内元素的位置和确切尺寸,所有的相对位置都会转变成屏幕上的绝对位置,在得知了节点是否可见、样式、位置几何信息之后,渲染引擎才有机会将 render tree 上的每个节点都转换成屏幕上的像素,这个过程也就是一般说的绘制(paint)或者栅格化(Rastering)。

layer tree 就在栅格化的过程当中。在说栅格化之前,有必要提一下 Chrome 是如何将渲染视口内的内容。

Chrome 采用了一种合成 composting 的方式,将页面中的某些部分分成不同的层,分别栅格化它们,然后在合成器线程中合成。这样在页面滚动时,原材料已经有了(准备好的那些层),只需要将视口内的重合成为一个新帧即可。这样在用户滚动时,新帧的合成效率更高。

既然需要分层,那就要知道那些元素应该在哪一层里,所以渲染引擎需要按照一定规则再遍历一次 layout tree 来创建 layer tree ,这个过程也就是 pre-paint,以前叫做 update layer tree。分层也需要按照一定的规则,不是任意一个元素都可以被拎出来当做一层,主要是有以下几种方式:

  • 使用了transform或opacity CSS 属性的元素。
  • 具有position: fixed或position: absolute的元素。
  • 使用了will-change属性告知浏览器元素可能发生变化的。
  • 具有 GPU 加速的 CSS 滤镜或 backdrop-filter 属性的元素。
  • 触发了硬件加速的复杂 CSS 动画或 JavaScript 动画的元素。

我们可以在 devtools 中的 3D View 栏看到页面实际上被分成了许多层。

请添加图片描述

点击具体图层,可以看到层级关系以及为什么会被提升一层的原因(Compositing Reasons):

请添加图片描述

Paint

通过前面分层,我们得知了元素的层级关系,但是还不知道同一层内元素的层级关系(z-index)。一般来说,后面的内容会覆盖前面内容,但是浏览器该如何知道谁该覆盖谁呢?这就需要渲染引擎为每一个图层创建绘制记录 patint record 并确定谁先画谁后画,那么后画的肯定就会覆盖先画的。

绘制记录可以看做一个单向链表 div -> div -> p -> span,遍历链表即可获得绘制顺序。

现在有了图层,也有了绘制记录顺序,这些信息将会被提交到合成器线程中进行绘图和合成。由于一个图层可能会非常大,超过了视口面积,那么图层就会经历一次分割过程,分割成一个个小的图块 Tile,通常是 256256 或 512512 大小,这些图块进行会传递给栅格化线程池。池中的栅格化线程执行栅格化任务 RasterTask,将图块生成位图 bitmap,并优先生成视口附近的位图。这个过程在 performamce 里叫做 Rasterize Paint。

请添加图片描述

从上图可以看到 Rasterize Paint 这个任务是在其他线程中运行的。那么也就不会影响 HTML 的解析。

栅格化过程也会使用 GPU 来加速,一般又称为快速栅格化,GPU 栅格化。这也是为什么会有些 css 里写 will-change:transfrom 或者 transform: translateZ(0),就是为了让 GPU 参与绘制。本质上是利用 will-change 和 translateZ(0) 创建了新的渲染层,从而不影响其他层级的绘制内容。

这个阶段给它换个名称可能我们就会感觉很熟悉 —— 重绘。

当 render tree 中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如 background-color、color 这些。则就叫称为重绘。

请添加图片描述

上面的例子是通过一个点击事件切换背景颜色,可以看到只经过了 Recalculate Style、pre-paint 阶段后就直接进行 paint,因为改变的只是元素外观,因此不需要重新排版,也就不需要经过 Layout 处理。

回流必将引起重绘,而重绘不一定会引起回流

减少回流和重绘的方法:

  • 尽可能在 DOM 树的最末端改变 class
  • 避免设置多层内联样式
  • 避免使用 table 布局
  • 对于复杂动画效果,使用绝对定位让其脱离文档流
  • 使用 css3 硬件加速,可以让 transform、opacity、filters 等动画效果不会引起回流重绘
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值