什么是 CRP?
CRP
又称关键渲染路径,引用MDN
对它的解释:css
❝关键渲染路径是指浏览器经过把 HTML、CSS 和 JavaScript 转化成屏幕上的像素的步骤顺序。优化关键渲染路径能够提升渲染性能。关键渲染路径包含了 Document Object Model (DOM),CSS Object Model (CSSOM),渲染树和布局。html
❞
优化关键渲染路径能够提高首屏渲染时间。理解和优化关键渲染路径对于确保回流和重绘能够每秒 60 帧、确保高性能的用户交互和避免无心义渲染相当重要。前端
如何结合CRP
进行性能优化?
我想对于性能优化,你们都不陌生,不管是平时的工做仍是面试,是一个老生常谈的话题。vue
若是单纯针对一些点去泛泛而谈,我想是不太严谨的。react
今天咱们结合一道很是经典的面试题:从输入URL到页面展现,这中间发生了什么?
来从其中的某些环节,来深刻谈谈前端性能优化 CRP
。web
从输入 URL 到页面展现,这中间发生了什么?
这道题的经典程度想必不用我多说,这里我用一张图梳理了它的大体流程:这个过程能够大体描述为以下:面试
一、URI 解析算法
二、DNS 解析(DNS 服务器)浏览器
三、TCP 三次握手(创建客户端和服务器端的链接通道)缓存
四、发送 HTTP 请求
五、服务器处理和响应
六、TCP 四次挥手(关闭客户端和服务器端的链接)
七、浏览器解析和渲染
八、页面加载完成
本文我会从浏览器渲染过程、缓存、DNS 优化几方面进行性能优化的说明。
浏览器渲染过程
构建 DOM 树
构建DOM
树的大体流程梳理为下图:
咱们如下面这段代码为例进行分析:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>构建DOM树</title>
</head>
<body>
<p>森林</p>
<div>之晨</div>
</body>
</html>
首先浏览器从磁盘或网络中读取 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
,若都改为内联模式,那么关键资源的个数就由 3 个减小到了 1 个。另外一种方式,若是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预解析
:
-
在页面头部加入,这样浏览器对整个页面进行预解析
<meta http-equiv="x-dns-prefetch-control" content="on">
-
经过 link 标签手动添加要解析的域名,好比:
<link rel="dns-prefetch" href="//img10.360buyimg.com"/>
参考
李兵 「浏览器工做原理与实践」
❤️ 爱心三连击
1.若是以为这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~
2.关注公众号前端森林,按期为你推送新鲜干货好文。
3.特殊阶段,带好口罩,作好我的防御。
4.添加微信fs1263215592,拉你进技术交流群一块儿学习 🍻
本文分享自微信公众号 - 全栈大佬的修炼之路(gh_7795af32a259)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。