前端性能优化

1,442 阅读19分钟

一、介绍

一个网站的体验,决定了用户是否愿意去了解网站的功能;而网站的功能,决定了用户是否会一票否决网站的体验。可见网站体验的重要性,而性能优化能大大提升网站的体验。

性能优化一直以来都是工程师界的“上乘武功”。因为它涉及的面比较广,而且要求也比较高,不但要知其然还要知其所以然。

网上关于性能优化的技巧有很多,技巧再多,他们所遵守的规则是不变的,都是围绕着页面的生命周期进行的,所以首先要了解页面的生命周期。

二、页面生命周期

从输入URL到看到内容,中间发生了什么?这是一道前后端通吃的可弹性的题,侧重点可以是前端也可以是后端,可大可小,可丰富可精简,就看自己的基本功如何了;

如下图所示:

image.png

image.png

1.开启网络请求线程

前提知识:进程、线程、进程和线程的关系、早期的单进程浏览器 VS 现在的多进程浏览器;

以谷歌浏览器为例,是一个多进程的浏览器,主要有:浏览器主进程、GPU进程、网络进程、渲染进程等;

image.png 前端人员最关心的是渲染进程。浏览器的每个窗口都是一个独立的渲染进程,主要用来处理页面渲染、脚本执行、事件处理等,页面生命周期的最后一个阶段“浏览器渲染页面”的时候会用到渲染进程。

渲染进程内部又是多线程的。

1)GUI 渲染线程:负责渲染浏览器界面,解析html、css、构建DOM树和Render树,布局和绘制等;

2)JS 引擎线程:与GUI线程互斥,常说的V8引擎就是在这里,是单线程的,负责处理 JS 脚本程序;

3)事件触发线程:归属于渲染进程,当js引擎忙不过来的时候,把客户端的触发事件收集起来放在任务队列中,等js引擎空闲时间处理;

4)定时器触发线程:用来给setTimeout或setInterval计时,计时完成后就会将特定事件推入事件触发线程的任务队列中,等待进入主线程执行;

5)异步http请求线程:在XMLHttpRequest连接后,通过浏览器新开一个线程请求;

查看浏览器的进程列表:谷歌浏览器→任务管理器

image.png 当用户输入url回车后,事件会进入浏览器主进程的UI线程中,UI线程会判断用户输入的是关键字进行搜索还是url进行访问,如果是url的话,主进程会开启一个network线程去获取网页;

netWork线程如何获取网页资源呢?URL是给人们看的,方便人们记忆,而计算机的端对端的通信是通过IP地址进行的,所以需要把URL解析成对应的IP地址才能进行通信。

2.DSN解析

DNS域名解析:用来把URL地址解析成IP地址;

分为:递归查询 和 迭代查询;

image.png

image.png

解析到服务器的IP地址以后,就可以建立端对端的通信链路了。

3.建立通信链路

前提需要了解一些网络模型的知识,以及每层都有哪些协议;

理想的OSI 7层模型 和 实际网络中使用的 TCP/IP 4层模型;

本地主机联网后自动打通 数据链路层 和 网络层,要向进行端对端的通信,还需要在传输层建立端对端的链接;

image.png

4.前后端通信

通信链路创建好之后,前后端就可以进行通信了,浏览器组织http报文发送给服务器,服务器接收到请求报文并进行处理,最后返回响应报文。

image.png

5.浏览器渲染页面

浏览器收到服务器的响应报文以后,开始渲染页面

image.png

浏览器渲染过程:

image.png

三、页面生命周期每个阶段的性能优化

性能优化存在于页面生命周期中的每个阶段,有些优化是浏览器做的(比如单进程的浏览器 到 多进程的浏览器,浏览器的缓存策略,浏览器的垃圾回收机制等),我们能做的是有效的利用浏览器的这些功能;还有一些优化则需要开发人员进行处理;

1.开启网络请求线程

这个阶段中的优化,基本上是浏览器自身的升级和优化,比如早期的单进程浏览器 和 现在的多进程浏览器;我们一般很难做点什么。

2.DSN解析

单个资源的典型DNS解析过程所花费的时间大概20-120 毫秒,很容易被人们忽略,但是当⽹站中使⽤的资源过多,而且依赖于多个不同的域的时候,时间就会成倍的增加,从⽽增加了⽹站的加载时间。

DNS解析这个阶段可优化的点有:

1)减少DNS的请求次数:

有效的利用浏览器对DNS的缓存,必要的话可以延长缓存时间;

减少域名的数量;

2)进行 DNS 预获取:dns-prefetch

<line rel="dns-prefetch" href="https://fonts.googleapis.com/">

3)预加载:

<link rel="preload" href="https://www.baidu.com">

4)预渲染:

<link rel="prerender" href="http://example.com">

3.建立通信链路

该阶段有哪些可有优化的点呢?

1)减少http的请求次数:

通信链路的创建(tcp3次握手)和销毁(tcp4次挥手)比较耗费时间,尽量减少http请求的次数;

合并小文件;

利用缓存:浏览器缓存、CDN、反向代理、本地缓存、分布式缓存、数据库缓存……

Ajax可缓存,能用get请求的尽量用get,发起相同的请求时,url和参数可以从缓存中取

2)http协议的选择:

http2.0 > http1.1 > http1.0

规则就是:能用高版本协议的就用高版本协议,因为高版本协议解决了低版本协议的一些痛点,进行了一些优化。

4.前后端通信

该阶段可优化的点:

image.png

5.浏览器渲染页面

该阶段的优化建议如下:

image.png

关于性能优化的技巧还有很多很多,像网络层面 & 渲染层面 & 文件优化层面的,比如:

1)懒加载

将不关键的资源延后加载,只加载可视区内需要的内容。

对于图片来说,先设置图片的src指向本地占位图,真实图片地址放在自定义属性中,当图片进入可视区时再将src替换成实际图片地址;(优点:图片会先下载资源,实现了图片懒加载)

2)懒执行

将某些逻辑延迟到使用时再计算。主要用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,可以使用懒执行;

懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒

3)WebPack打包工具的优化配置项:

tree shaking 移除没有使用的代码

拆分模块,按需加载

等等,其根本都是围绕页面生命周期进行的优化。

当我们做了很多的优化项后想要看一下效果,除了肉眼的观察,有没有一些官方的通用的性能的指标来评判我们优化的力度呢?

四、Web性能指标

1.RAIL性能模型

RAIL 是 Response, Animation, Idle,Load 的⾸字⺟缩写, 是⼀种由 Google Chrome 团队于 2015 年提出的性能模型, ⽤于提升浏览器内的⽤户体验和性能。

RAIL 模型的理念是”以⽤户为中⼼,最终⽬标不是让您的⽹站在任何特定设备上都能运⾏很快,⽽是使⽤户满意”。

这个名字的由来是四个英⽂单词的⾸字⺟:

  1. 响应(Response):应该尽可能快速的响应⽤户, 应该在 100ms 以内响应⽤户输⼊。

  2. 动画(Animation):在展示动画的时候,每⼀帧应该以 16ms 进⾏渲染,这样可以保持动画效果 的⼀致性,并且避免卡顿。

  3. 空闲(Idle):当使⽤ Javascript 主线程的时候,应该把任务划分到执⾏时间⼩于 50ms 的⽚段中

去,这样可以释放线程以进⾏⽤户交互。

  1. 加载(Load):应该在⼩于 1s 的时间内加载完成你的⽹站,并可以进⾏⽤户交互。

这四个单词代表与⽹站或应⽤的⽣命周期相关的四个⽅⾯,这些⽅⾯会以不同的⽅式影响整个⽹站的性

能。

我们将⽤户作为性能优化的中⼼,⾸先需要了解⽤户对于延迟的反应。⽤户感知延迟的时间窗⼝,如下表所示。

延迟用户反馈
0 ~ 16ms人眼可以感知每秒 60 帧的动画,即每桢 16ms,除了浏览器将一帧画面绘制到屏幕上的时间,网站应用大约需要 10ms 来生成一帧
0 ~ 100ms在该时间范围内响应用户操作,才会是流畅的体验
100 ~ 1000ms能够感觉到明显的延迟
>1s用户的注意力将离开对执行任务的关注
>10s用户感到失望,可能会放弃任务

响应

指标:应该尽可能快速的响应⽤户,应该在 100ms 以内响应⽤户输⼊。

⽹站性能对于响应⽅⾯的要求是,在⽤户感知延迟之前接收到操作的反馈。⽐如⽤户进⾏了⽂本输⼊、按钮单击、表单切换及启动动画等操作后,必须在 100ms 内收到反馈,如果超过 100ms 的时间窗⼝,⽤户就会感知延迟。

看似很基本的⽤户操作背后,可能会隐藏着复杂的业务逻辑处理及⽹络请求与数据计算。对此我们应当谨慎,将较⼤开销的⼯作放在后台异步执⾏,⽽即便后台处理要数百毫秒才能完成的操作,也应当给⽤户提供及时的阶段性反馈。

⽐如在单击按钮向后台发起某项业务处理请求时,⾸先反馈给⽤户开始处理的提示,然后在处理完成的回调后反馈完成的提示。

动画

指标:在展示动画的时候,每⼀帧应该以 10ms 进⾏渲染,这样可以保持动画效果的⼀致性,并且避免卡顿。

前端所涉及的动画不仅有炫酷的UI特效,还包括滚动和触摸拖动等交互效果,⽽这⼀⽅⾯的性能要求就是流畅。众所周知,⼈眼具有视觉暂留特性,就是当光对视⽹膜所产⽣的视觉在光停⽌作⽤后,仍能保留⼀段时间。

研究表明这是由于视神经存在反应速度造成的,其值是 1/24s,即当我们所⻅的物体移除后,该物体在我们眼中并不会⽴即消失,⽽会延续存在 1/24s 的时间。对动画来说,⽆论动画帧率有多⾼,最后我们仅能分辨其中的 30 帧,但越⾼的帧率会带来更好的流畅体验,因此动画要尽⼒达到 60fps 的帧率。

⽬前⼤多数设备的屏幕刷新率为 60 次/秒,那么浏览器渲染动画或⻚⾯的每⼀帧的速率也需要跟设备屏幕的刷新率保持⼀致。所以根据 60fps 帧率的计算,每⼀帧画⾯的⽣成都需要经过若⼲步骤,⼀帧图像的⽣成预算为 16ms(1000ms / 60 ≈ 16.66ms),除去浏览器绘制新帧的时间,留给执⾏代码的时间仅 10ms 左右。如果⽆法符合此预算,帧率将下降,并且内容会在屏幕上抖动。 此现象通常称为卡顿,会对⽤户体验产⽣负⾯影响。

googlechrome.github.io/devtools-sa…

空闲

指标:当使⽤ Javascript 主线程的时候,应该把任务划分到执⾏时间⼩于 50ms 的⽚段中去,这样可以

释放线程以进⾏⽤户交互。

要使⽹站响应迅速、动画流畅,通常都需要较⻓的处理时间,但以⽤户为中⼼来看待性能问题,就会发现并⾮所有⼯作都需要在响应和加载阶段完成,我们完全可以利⽤浏览器的空闲时间处理可延迟的任务,只要让⽤户感受不到延迟即可。利⽤空闲时间处理延迟,可减少预加载的数据⼤⼩,以保证⽹站或应⽤快速完成加载。

为了更加合理地利⽤浏览器的空闲时间,最好将处理任务按 50ms 为单位分组。这么做就是保证⽤户在发⽣操作后的 100ms 内给出响应。

加载

指标:⾸次加载应该在⼩于 5s 的时间内加载完成,并可以进⾏⽤户交互。对于后续加载,则是建议在2秒内完成。

⽤户感知要求我们尽量在 5s 内完成⻚⾯加载,如果没有完成,⽤户的注意⼒就会分散到其他事情上,并 对当前处理的任务产⽣中断感。需要注意的是,这⾥在 5s 内完成加载并渲染出⻚⾯的要求,并⾮要完成 所有⻚⾯资源的加载,从⽤户感知体验的⻆度来说,只要关键渲染路径完成,⽤户就会认为全部加载已完成。

对于其他⾮关键资源的加载,延迟到浏览器空闲时段再进⾏,是⽐较常⻅的渐进式优化策略。⽐如图⽚懒加载、代码拆分等优化⼿段。

2.基于用户体验的性能指标

基于用户体验的性能指标是 Google 在 web.dev 提出的。

First Contentful Paint 首次内容绘制 (FCP)

FCP:页面从开始加载到用户首次在屏幕上看到任何内容时的时间,"内容"指的是文本、图像(包括背景图像)、<svg>元素或非白色的<canvas>元素。

FCP 发生在第二帧,因为那是首批文本和图像元素在屏幕上完成渲染的时间点。

速度指标

FCP 时间(以秒为单位)颜色编码FCP分数 (HTTP存档百分位数)
0 - 2绿色 (快速)75 - 100
2 - 4橙色 (中等)50 - 74
超过4红色 (慢)0 - 49

参考链接: web.dev/fcp/

Largest Contentful Paint 最大内容绘制 (LCP)

LCP:可视区域中最大的内容元素(文本或图像)呈现到屏幕上的时间,用以估算页面的主要内容对用户可见时间。

LCP考虑的元素:

  • <img>元素
  • 内嵌在<svg>元素内的<image>元素
  • <video>元素(使用封面图像)
  • 通过url()函数(而非使用CSS 渐变)加载的带有背景图像的元素
  • 包含文本节点或其他行内级文本元素子元素的块级元素

示例:最大内容绘制的时间点

最大元素随内容加载而变化。在第一个示例中,新内容被添加进 DOM,并因此使最大元素发生了改变。在第二个示例中,由于布局的改变,先前的最大内容从可视区域中被移除。

虽然延迟加载的内容通常比页面上已有的内容更大,但实际情况并非一定如此。接下来的两个示例显示了在页面完全加载之前出现的最大内容绘制。

速度指标

参考链接: web.dev/lcp/

First Input Delay 首次输入延迟 (FID)

FID:从⽤户第⼀次与⻚⾯交互(例如单击链接、点击按钮等)到浏览器实际能够响应该交互的时间。

输⼊延迟是因为浏览器的主线程正忙于做其他事情,所以不能响应⽤户。发⽣这种情况的⼀个常⻅原因是浏览器正忙于解析和执⾏应⽤程序加载的⼤量计算的 JavaScript。

第⼀次输⼊延迟通常发⽣在第⼀次内容绘制(FCP)和可持续交互时间(TTI)之间,因为⻚⾯已经呈现了⼀些内容,但还不能可靠地交互。

如上图所示,浏览器接收到⽤户输⼊操作时,主线程正在忙于执⾏⼀个耗时⽐较⻓的任务,只有当这个任务执⾏完成后,浏览器才能响应⽤户的输⼊操作。它必须等待的时间就此⻚⾯上该⽤户的 FID 值。

参考链接web.dev/fid/

Time to Interactive 可交互时间 (TTI)

TTI:表示⽹⻚第⼀次 完全达到可交互状态 的时间点,浏览器已经可以持续性的响应⽤户的输⼊。完全达到可交互状态的时间点是在最后⼀个⻓任务(Long Task)完成的时间, 并且在随后的 5 秒内⽹络和主线程是空闲的。

从定义上来看,中⽂名称叫可持续交互时间或可流畅交互时间更合适。

长任务:需要50ms以上才能完成的任务;

速度指标

TTL 指标(以秒为单位)颜色编码
0 - 3.8绿色 (快速)
3.9 - 7.3橙色 (中等)
7.3以上红色 (慢)

参考链接web.dev/tti/

Total Blocking Time 总阻塞时间 (TBT)

TBT:度量FCP 和 TTI 之间的总时间,在该时间范围内,主线程被阻塞足够长的时间以防止输入响应。

只要存在⻓任务,该主线程就会被视为“阻塞”,该任务在主线程上运⾏超过50毫秒(ms)。我们说主线 程“被阻⽌”是因为浏览器⽆法中断正在进⾏的任务。因此,如果⽤户确实在较⻓的任务中间与⻚⾯进⾏交互,则浏览器必须等待任务完成才能响应。

如果任务⾜够⻓(例如,超过50毫秒的任何时间),则⽤户很可能会注意到延迟并感觉⻚⾯缓慢或过时。

给定的⻓任务的阻⽌时间是其持续时间超过50毫秒。⻚⾯的总阻塞时间是FCP和TTI之间发⽣的每个⻓任务的阻塞时间的总和。

例如,考虑⻚⾯加载期间浏览器主线程的下图:

上⾯的时间轴有五个任务,其中三个是⻓任务,因为它们的持续时间超过50毫秒。下图显示了每个⻓任务的阻塞时间:

因此,虽然在主线程上运⾏任务花费的总时间为560毫秒,但只有345毫秒的时间被视为阻塞时间。

速度指标

TBT 时间(以毫秒为单位)颜色编码
0 - 300绿色 (快速)
300 - 600橙色 (中等)
超过600红色 (慢)

参考链接web.dev/tbt/

Cumulative Layout Shift 累积布局偏移 (CLS)

CLS:整个页面生命周期内发生的所有意外布局偏移中最大一连串的布局偏移分数,它是⼀种保证⻚⾯的视觉稳定性从⽽提升⽤户体验的指标⽅案。

您是否曾经在⻚⾯上突然发⽣变化时在没有警告的情况下,⽂字移动了,并且您失去了位置。甚⾄更糟:您将要点击⼀个链接或⼀个按钮,但是在⼿指落下的瞬间,链接移动了,您最终单击了其他东⻄!

⻚⾯内容的意外移动通常是由于异步加载资源或将 DOM 元素动态添加到现有内容上⽅的⻚⾯⽽发⽣的。

罪魁祸⾸可能是尺⼨未知的图像或视频,呈现⽐其后备更⼤或更⼩的字体,或者是动态调整⾃身⼤⼩的第三⽅⼴告或⼩部件。

速度指标

参考链接web.dev/cls/

Speed Index 速度指数

Speed Index(速度指数)是一个表示页面可视区域中内容的填充速度的指标,可以通过计算页面可见区域内容显示的平均时间来衡量。

参考链接web.dev/speed-index…

3、新一代性能指标 Web vitals

Google 开发了许多实⽤指标和⼯具,帮助衡量⽤户体验和质量,从⽽发掘优化点。⼀项名为 Web Vitals的计划降低了学习成本,为⽹站体验提供了⼀组统⼀的质量衡量指标 — Core Web Vitals,其中包括加载 体验、交互性和⻚⾯内容的视觉稳定性。

有很多⽅法可以优化⽹站的⽤户体验。若能预先了解最佳的优化衡量⽅法,可以⼤⼤节省时间和成本。

Google 在 2020 年 5 ⽉ 5 ⽇提出了新的⽤户体验量化⽅式 Web Vitals 来衡量⽹站的⽤户体验,并将这

些衡量结果⽤作其排名算法的⼀部分。为了更好的理解这些内容,让我们来看看这些重要指标是什么。

Core Web Vitals 与 Web Vitals

什么是 Web Vitals,Google 给出的定义是 ⼀个良好⽹站的基本指标(Essential metrics for a healthy

site),过去要衡量⼀个⽹站的好坏,需要使⽤的指标太多了,Web Vitals 可以简化指标的学习曲线,只需聚焦于 Web Vitals 指标的表现即可。

在这些 Web Vitals 中,Google 确定了三个主要衡量指标,即在所有类型的⽹站中通⽤的 Core Web

Vitals:

LCP:最大内容绘制

FID:首次输入延迟

CLS:累计布局偏移

参考链接web.dev/vitals/

五、LightHouse-灯塔

谷歌浏览器自带的一个性能检测工具。

会有一个综合评分;6个性能指标的各自成绩;还有一些针对性的优化建议(预估的可节省的时间从长到短进行排序的);