聊一聊前端性能优化 CRP

14 篇文章 0 订阅
7 篇文章 0 订阅

什么是 CRP?
CRP 又称关键渲染路径,引用 MDN 对它的解释:

关键渲染路径是指浏览器通过把 HTML、CSS 和 JavaScript 转化成屏幕上的像素的步骤顺序。优化关键渲染路径可以提高渲染性能。关键渲染路径包含了 Document Object Model (DOM),CSS Object Model (CSSOM),渲染树和布局。

优化关键渲染路径可以提升首屏渲染时间。理解和优化关键渲染路径对于确保回流和重绘可以每秒 60 帧、确保高性能的用户交互和避免无意义渲染至关重要。

如何结合 CRP 进行性能优化?
我想对于性能优化,大家都不陌生,无论是平时的工作还是面试,是一个老生常谈的话题。

如果单纯针对一些点去泛泛而谈,我想是不太严谨的。

今天我们结合一道非常经典的面试题: 从输入URL到页面展示,这中间发生了什么? 来从其中的某些环节,来深入谈谈 前端性能优化 CRP 。

从输入 URL 到页面展示,这中间发生了什么?
这道题的经典程度想必不用我多说,这里我用一张图梳理了它的大致流程:
在这里插入图片描述
这个过程可以大致描述为如下:

1、URI 解析

2、DNS 解析(DNS 服务器)

3、TCP 三次握手(建立客户端和服务器端的连接通道)

4、发送 HTTP 请求

5、服务器处理和响应

6、TCP 四次挥手(关闭客户端和服务器端的连接)

7、浏览器解析和渲染

8、页面加载完成

本文我会从浏览器渲染过程、缓存、DNS 优化几方面进行性能优化的说明。

浏览器渲染过程
构建 DOM 树
构建 DOM 树的大致流程梳理为下图:
在这里插入图片描述
我们以下面这段代码为例进行分析:

构建DOM树

森林

之晨
复制代码 首先浏览器从磁盘或网络中读取 HTML 原始字节,并根据文件的指定编码将它们转成字符。

然后通过分词器将字节流转换为 Token ,在 Token (也就是令牌)生成的同时,另一个流程会同时消耗这些令牌并转换成 HTML head 这些节点对象,起始和结束令牌表明了节点之间的关系。
在这里插入图片描述
当所有的令牌消耗完以后就转换成了 DOM (文档对象模型)。

最终构建出的 DOM 结构如下:
在这里插入图片描述
构建 CSSOM 树
DOM 树构建完成,接下来就是 CSSOM 树的构建了。

与 HTML 的转换类似,浏览器会去识别 CSS 正确的令牌,然后将这些令牌转化成 CSS 节点。

子节点会继承父节点的样式规则,这里对应的就是层叠规则和层叠样式表。

构建 DOM 树的大致流程可梳理为下图:
在这里插入图片描述
我们这里采用上面的 HTML 为例,假设它有如下 css:

body {
font-size: 16px;
}
p {
font-weight: bold;
}
div {
color: orange;
}
复制代码
那么最终构建出的 CSSOM 树如下:
在这里插入图片描述
有了 DOM 和 CSSOM ,接下来就可以合成布局树(Render Tree)了。

构建渲染树
等 DOM 和 CSSOM 都构建好之后,渲染引擎就会构造布局树。布局树的结构基本上就是复制 DOM 树的结构,不同之处在于 DOM 树中那些不需要显示的元素会被过滤掉,如 display:none 属性的元素、 head 标签、 script 标签等。

复制好基本的布局树结构之后,渲染引擎会为对应的 DOM 元素选择对应的样式信息,这个过程就是样式计算。

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

把 CSS 转换为浏览器能够理解的结构

和 HTML 文件一样,浏览器也是无法直接理解这些纯文本的 CSS 样式,所以当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构—— styleSheets 。

转换样式表中的属性值,使其标准化

现在我们已经把现有的 CSS 文本转化为浏览器可以理解的结构了,那么接下来就要对其进行属性值的标准化操作。

什么是属性值标准化?我们来看这样的一段 CSS :

body {
font-size: 2em;
}
div {
font-weight: bold;
}
div {
color: red;
}
复制代码
可以看到上面的 CSS 文本中有很多属性值,如 2em、bold、red,这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。

那标准化后的属性值是什么样子的?
在这里插入图片描述
从图中可以看到, 2em 被解析成了 32px , bold 被解析成了 700 , red 被解析成了 rgb(255,0,0) ……

计算出 DOM 树中每个节点的具体样式

现在样式的属性已被标准化了,接下来就需要计算 DOM 树中每个节点的样式属性了,如何计算呢?

这其中涉及到两点:CSS 的 继承规则 和 层叠规则 。

这里由于不是本文的重点,我简单做下说明:

CSS 继承就是每个 DOM 节点都包含有父节点的样式
CSS
CSS
CSS
样式计算完成之后,渲染引擎还需要计算布局树中每个元素对应的几何位置,这个过程就是计算布局。

计算布局
现在,我们有 DOM 树和 DOM 树中元素的样式,但这还不足以显示页面,因为我们还不知道 DOM 元素的几何位置信息。那么接下来就需要计算出 DOM 树中可见元素的几何位置,我们把这个计算过程叫做 布局 。

绘制
通过样式计算和计算布局就完成了最终布局树的构建。再之后,就该进行后续的绘制操作了。

到这里,浏览器的渲染过程就基本结束了,通过下面的一张图来梳理下:
在这里插入图片描述
到这里我们已经把浏览器解析和渲染的完整流程梳理完成了,那么这其中有那些地方可以去做性能优化呢?

从浏览器的渲染过程中可以做的优化点
通常一个页面有三个阶段:加载阶段、交互阶段和关闭阶段。

加载阶段,是指从发出请求到渲染出完整页面的过程,影响到这个阶段的主要因素有网络和 JavaScript 脚本。
交互阶段,主要是从页面加载完成到用户交互的整合过程,影响到这个阶段的主要因素是 JavaScript 脚本。
关闭阶段,主要是用户发出关闭指令后页面所做的一些清理操作。

这里我们需要重点关注 加载阶段 和 交互阶段 ,因为影响到我们体验的因素主要都在这两个阶段,下面我们就来逐个详细分析下。

加载阶段
我们先来分析如何系统优化加载阶段中的页面,来看一个典型的渲染流水线,如下图所示:
在这里插入图片描述
通过上面对浏览器渲染过程的分析我们知道 JavaScript 、首次请求的 HTML 资源文件、 CSS 文件是会阻塞首次渲染的,因为在构建 DOM 的过程中需要 HTML 和 JavaScript 文件,在构造渲染树的过程中需要用到 CSS 文件。

这些能阻塞网页首次渲染的资源称为 关键资源 。而基于关键资源,我们可以继续细化出三个影响页面首次渲染的核心因素:

关键资源个数 。关键资源个数越多,首次页面的加载时间就会越长。
关键资源大小 。通常情况下,所有关键资源的内容越小,其整个资源的下载时间也就越短,那么阻塞渲染的时间也就越短。
请求关键资源需要多少个RTT(Round Trip Time) 。 RTT 是网络中一个重要的性能指标,表示从发送端发送数据开始,到发送端收到来自接收端的确认,总共经历的时延。
了解了影响加载过程中的几个核心因素之后,接下来我们就可以系统性地考虑优化方案了。总的优化原则就是 减少关键资源个数 , 降低关键资源大小 , 降低关键资源的 RTT 次数 :

JavaScript
CSS
JavaScript
CSS
JavaScript
DOM
CSSOM
sync
defer
CSS
JavaScript
HTML
CSS
JavaScript
RTT
CDN
RTT
交互阶段
接下来我们再来聊聊页面加载完成之后的交互阶段以及应该如何去优化。

先来看看交互阶段的渲染流水线:
在这里插入图片描述
其实这块大致有以下几点可以优化:

避免DOM的回流 。也就是尽量避免 重排 和 重绘 操作。

减少 JavaScript 脚本执行时间 。有时 JavaScript 函数的一次执行时间可能有几百毫秒,这就严重霸占了主线程执行其他渲染任务的时间。针对这种情况我们可以采用以下两种策略:

一种是将一次执行的函数分解为多个任务,使得每次的执行时间不要过久。

另一种是采用 Web Workers 。
DOM操作相关的优化 。浏览器有 渲染引擎 和 JS引擎 ,所以当用 JS 操作 DOM 时,这两个引擎要通过接口互相“交流”,因此每一次操作 DOM (包括只是访问 DOM 的属性),都要进行引擎之间解析的开销,所以常说要减少 DOM 操作。总结下来有以下几点:

缓存一些计算属性,如 let left = el.offsetLeft 。
DOM
class
style
分离读写操作。现代的浏览器都有渲染队列的机制。

DOM
vue/react
virtual dom
合理利用 CSS 合成动画 。合成动画是直接在合成线程上执行的,这和在主线程上执行的布局、绘制等操作不同,如果主线程被 JavaScript 或者一些布局任务占用, CSS 动画依然能继续执行。所以要尽量利用好 CSS 合成动画,如果能让 CSS 处理动画,就尽量交给 CSS 来操作。

CSS选择器优化 。我们知道 CSS引擎 查找是从右向左匹配的。所以基于此有以下几条优化方案:

尽量不要使用通配符

少用标签选择器

尽量利用属性继承特性

CSS属性优化 。浏览器绘制图像时, CSS 的计算也是耗费性能的,一些属性需浏览器进行大量的计算,属于昂贵的属性( box-shadows 、 border-radius 、 transforms 、 filters 、 opcity 、 :nth-child 等),这些属性在日常开发中经常用到,所以并不是说不要用这些属性,而是在开发中,如果有其它简单可行的方案,那可以优先选择没有昂贵属性的方案。

避免频繁的垃圾回收 。我们知道 JavaScript 使用了自动垃圾回收机制,如果在一些函数中频繁创建临时对象,那么垃圾回收器也会频繁地去执行垃圾回收策略。这样当垃圾回收操作发生时,就会占用主线程,从而影响到其他任务的执行,严重的话还会让用户产生掉帧、不流畅的感觉。

缓存
缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。下图是浏览器缓存的查找流程图:
在这里插入图片描述
浏览器缓存相关的知识点还是很多的,这里我有整理一张图:
在这里插入图片描述
关于浏览器缓存的详细介绍说明,可以参考我之前的这篇文章,这里就不赘述了。

DNS 相关优化
DNS 全称 Domain Name System 。它是互联网的“通讯录”,它记录了域名与实际 ip 地址的映射关系。每次我们访问一个网站,都要通过各级的 DNS 服务器查询到该网站的服务器 ip ,然后才能访问到该服务器。

DNS 相关的优化一般涉及到两点:浏览器 DNS 缓存和 DNS 预解析。

DNS 缓存
一图胜千言:
在这里插入图片描述
浏览器会先检查浏览器缓存(浏览器缓存有大小和时间限制),时间过长可能导致 IP 地址变化,无法解析正确 IP 地址,过短就会让浏览器重复解析域名,一般为几分钟。
如果浏览器缓存没有对应域名,则会去操作系统缓存中查找。

如果还没有找到,域名就会发送到本地区的域名服务器(一般由互联网供应商提供,电信、联通之类),一般在本地区的域名服务器上都能找到了。

当然也可能本地域名服务器也没找到,那本地域名服务器就开始递归查找。

一般而言,浏览器解析 DNS 需要 20-120ms ,因此 DNS 解析可优化之处几乎没有。但存在这样一个场景,网站有很多图片在不同域名下,那如果在登录页就提前解析了之后可能会用到的域名,使解析结果缓存过,这样缩短了 DNS 解析时间,提高网站整体上的访问速度了,这就是 DNS预解析 。

DNS 预解析
来看下 MDN 对于 DNS预解析 的定义吧:


X-DNS-Prefetch-Control 头控制着浏览器的 DNS 预读取功能。 DNS 预读取是一项使浏览器主动去执行域名解析的功能,其范围包括文档的所有链接,无论是图片的, CSS 的,还是 JavaScript 等其他用户能够点击的 URL 。


因为预读取会在后台执行,所以 DNS 很可能在链接对应的东西出现之前就已经解析完毕。这能够减少用户点击链接时的延迟。

我们这里就简单看一下如何去做 DNS预解析 :

在页面头部加入,这样浏览器对整个页面进行预解析

复制代码 通过 link 标签手动添加要解析的域名,比如: 复制代码

可以添加前端学习群:1017810018 大家一起学习(群主会不定时更新学习资料,以及面试题文档)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值