前言
我们通常讲的前端性能优化就是为了实现三个目标:展示快、交互快、无卡顿,为了实现这一点,相信大家已经做了很多的优化。最近我在学习前端性能优化方面的知识,看了很多的文章,同时自己也做了整理和归纳,本篇内容作为学习整理的输出,希望可以帮助到你。
注:本文为一个系列的开头(新坑),文章字数较多(接近 10k),请您抽出一块完整的时间来查看和做笔记,这样效果更好!
1. 使用 HTTP2 / HTTP 3
HTTP 2
为什么要把 HTTP 2 放第一个说,因为它真的很重要,可以很大程度提升请求的响应以及强缓存的命中(下面有讲),HTTP2 相比 HTTP1.1 有如下几个优点:
解析速度快
服务器解析 HTTP1.1 的请求时,必须不断地读入字节,直到遇到分隔符 CRLF 为止。而解析 HTTP2 的请求就不用这么麻烦,因为 HTTP2 是基于帧的协议,每个帧都有表示帧长度的字段。
多路复用
HTTP1.1 如果要同时发起多个请求,就得建立多个 TCP 连接,因为一个 TCP 连接同时只能处理一个 HTTP1.1 的请求。 在 HTTP2 上,多个请求可以共用一个 TCP 连接,这称为==多路复用==。同一个请求和响应用一个流(Stream)来表示,每个用户的操作行为被分配了一个流编号(Stream ID),这意味着用户与服务端之间建立了一个 TCP 通道;每个流中可以传输若干消息(Message),每个消息由若干最小的**二进制帧(Frame)**组成。多个请求和响应在 TCP 连接中可以乱序发送,到达目的地后再通过流 ID 重新组建。
高兼容性
HTTP/2 保留了 HTTP/1.1 的大部分语义,例如请求方法、状态码乃至 URI 和绝大多数 HTTP 头字段一致。而 HTTP/2 采用了新的方法来编码、传输客户端和服务器之间的数据。
HPACK 和服务端推送
HPACK 算法是新引入 HTTP/2 的一个算法,用于对 HTTP 头部做压缩。
网站为了使请求数减少,通常采用对页面上的图片、脚本进行压缩处理(Minify 和 雪碧图)。但是,这一举措并不高效,依然需要诸多 HTTP 连接来加载页面和页面资源。
HTTP/2 中引入了服务器推送,即服务端向客户端发送比客户端请求更多的数据。这允许服务器直接提供浏览器渲染页面所需资源,而无须浏览器在收到、解析页面后再提起一轮请求,节约了加载时间。
HTTP 3
欸,看起来 HTTP 2 很美好,为什么还需要 HTTP 3 呢?
HTTP/3 将是自 2015 年 HTTP/2 获得批准以来对超文本传输协议的第一次重大升级。
—— CLoudflare 官网百科
HTTP/3 的一个重要区别是它在一种新的传输协议 QUIC 上运行。QUIC 专为移动密集型互联网使用而设计,在这种环境中,人们携带的智能手机会在一天中不断地从一个网络切换到另一个网络。这和开发第一个互联网协议时情况完全不同:当时设备的便携性较差,也不会经常切换网络。
QUIC 的使用意味着 HTTP/3 依赖于用户数据报协议 (UDP),而不是传输控制协议 (TCP)。切换到 UDP 将使在线浏览时的连接速度和用户体验更快。
此变化主要为了解决 HTTP/2 中存在的队头阻塞问题。由于 HTTP/2 在单个 TCP 连接上使用了多路复用,受到 TCP 拥塞控制的影响,少量的丢包就可能导致整个 TCP 连接上的所有流被阻塞。
QUIC 将帮助修复 HTTP/2 的一些重大缺点:
- 解决切换网络时卡顿:解决当智能手机从 WiFi 切换到移动数据时性能缓慢的问题
- 减少丢包的影响:当一个信息包没有到达目的地时,它不会再阻塞所有的信息流(也被称为“队头阻塞”)
- 零往返时间 (0-RTT):对于它们已经连接的服务器,客户端可以跳过握手要求
- 更全面的加密:QUIC 的新握手方式将默认提供加密,这是对 HTTP/2 的巨大升级,并将有助于减轻攻击的风险
那么,代价是什么呢?
很明显的一点就是,在 HTTP/3 发布后,整个网络不会一下子转换过来。因为许多网站甚至还没有使用 HTTP/2。
新协议的一个潜在障碍是它需要增加服务器和客户端的 CPU 使用率。随着技术的发展(产品的迭代升级等),这可能会随着时间的推移而减少影响。
虽然该标准仍在开发中,但网站所有者和访问者可以开始通过浏览器、操作系统和其他客户端获得对 HTTP/3 的支持。
2. 使用服务端渲染
把服务端渲染(SSR,Server Ride Render)放在第二的位置是因为在我过去的开发中,SSR 帮助了我很多。正如你现在看到的博客就是一个典型的服务端渲染的例子:服务端渲染完成返回 HTML 文件,客户端只需解析 HTML 而不需要执行大量的 JavaScript 脚本阻塞页面加载。
优点:首屏渲染快,==SEO 好==,生成的内容可以被 CDN 等==缓存==,提高加载速度。 缺点:部分项目配置麻烦,同时增加了服务器的计算压力(可以通过提高 CDN 缓存 TTL 解决)。
客户端渲染的网站会直接返回 HTML 文件,而服务端渲染的网站则会渲染完页面再返回渲染好的 HTML 文件。服务端渲染在 Vue 项目中非常常见,也是一种比较推荐的方式。
这样做的好处是什么?是更快的内容输出时间。
设想这样一个场景,用户访问你的网站需要 QWERTY 这 6 个资源文件,而服务端渲染可以提前 fetch 这些文件并解析渲染输出到最终的一个 HTML 页面中,极大的提升了用户的体验。
Vue 参考文档:Server-Side Rendering (SSR)
3. 静态资源使用 CDN
这个想必都不陌生了吧,可以参考我之前推荐的一些 CDN,这里给出一些更好的优化建议:
- 长时间不更新的文件使用强缓存:一些库文件很可能之后就一直不会更新了,可以考虑强缓存半年。
- 一直会变的文件使用路径哈希:推荐在文件中使用诸如
index.114514.js
的形式(114514
就是文件的短哈希),这样去修改这个文件的内容,只需要更新路径哈希即可让客户端知道是新的内容(通常为打包程序自动生成,这里推荐 Webpack 的contenthash
和chunkhash
)。 - 使用 WebP 格式图片:部分 CDN 支持一键开启,WebP 格式图片会比正常的图片小很多,可以考虑实践一下。
- 开启服务端压缩:墙裂推荐每个使用了 CDN 全站加速的网站都开启 GZip 压缩!
- 善用缓存:为了避免用户每次访问网站都得请求文件,我们可以通过添加
Expires
或max-age
来控制这一行为。Expires
设置了一个时间,只要在这个时间之前,浏览器都不会请求文件,而是直接使用本地缓存。而max-age
是一个相对时间,建议使用max-age
代替Expires
。
4. 使用字体图标代替图片图标
设想一下,仅加载一个字体和加载一堆图片相比,哪一个对用户体验更好?
答案当然是前者,同时加载一堆图片卡不说,有时候还会造成页面堵塞白屏。字体图标就是将图标制作成一个字体,使用时就跟字体一样,可以设置属性,例如 font-size
、color
等等,非常方便。并且字体图标都是矢量图,不会在不同设备上失真(有锯齿感),还有一个优点是生成的文件真的特别小。
这里推荐我看到的一个压缩现有字体图标的小工具:https://github.com/patrickhulce/fontmin-webpack
5. 减少 JavaScript 动画
尽量避免添加大量的 JavaScript 动画,通常情况下 CSS3 动画甚至是 Canvas 动画都比 JS 动画性能好。
个人博客么也不要添加一大堆什么樱花特效(建议是默认关闭 + 按需加载),看着卡不说,读文章还会被挡住!!!(生气)
6. 节流和防抖
这部分参考 🔥 前端性能优化最佳实践
6.1 节流 Throttle
// 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。防止用户重复点击按钮造成后端请求压力!
function throttle(func, delay) {
let previousTime = Date.now(); // 当前时间
return function (...args) {
const context = this;
let curTime = Date.now();
if (curTime - previousTime > delay) {
previousTime = curTime;
func.call(context, ...args);
}
};
}
6.2 防抖 Debounce
// 在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。
function debounce(func, delay) {
let time = null;
return function (...args) {
const context = this;
if (time) {
clearTimeout(time);
}
time = setTimeout(() => {
func.call(context, ...args);
}, delay);
};
}
7. 改变 JS 阻塞的方式
7.1 defer 模式
defer 方式加载 script
, 不会阻塞 HTML 解析,等到 DOM 生成完毕且 script
加载完毕再执行 JS。
<script defer></script>
7.2 async 模式
async 表示异步执行引入的 JS,加载时不会阻塞 HTML 解析,但是加载完成后立马执行,此时仍然会==阻塞 load
事件==。
<script async></script>
7.3 将 CSS 放在文件头部,JavaScript 文件放在底部
- CSS 执行会阻塞渲染,阻止 JS 执行
- JS 加载和执行会阻塞 HTML 解析,阻止 CSSOM 构建
如果这些 CSS、JS 标签放在 HEAD 标签里,并且需要加载和解析很久的话,那么页面就空白了。所以 JS 文件要放在底部(不阻止 DOM 解析,但会阻塞渲染),等 HTML 解析完了再加载 JS 文件,尽早向用户呈现页面的内容。
那为什么 CSS 文件还要放在头部呢?
因为先加载 HTML 再加载 CSS,会让用户第一时间看到的页面是没有样式的、“丑陋”的,为了避免这种情况发生,就要将 CSS 文件放在头部了。
参考链接:前端性能优化 24 条建议(2020)
8. 优化项目的图片资源
8.1 懒加载
可以使用如下代码实现懒加载的效果:
<img data-src="https://github.com/xiaozhu2007.png" src="https://c.932686.xyz/ad-img/PlaceHolder.1280x640.png">
<!-- 其中 data-src 是真正要加载的地址 src 则是更小的,在未出现在显示区域加载的 placeholder 如果你有特殊要求甚至不需要写 src -->
// ... 当图片可见时
const img = document.querySelector('img')
img.src = img.dataset.src
// ...
8.2 WebP
WebP 是 Google 团队开发的加快图片加载速度的图片格式,其优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都相当优秀、稳定和统一。
8.3 压缩图片
可以使用在线网站,CLI 工具甚至是 Github 的 ImgBot 进行压缩
8.4 使用 CSS3
有很多图片使用 CSS 效果(渐变、阴影甚至是扫光等)就能画出来,这种情况选择 CSS3 效果更好。因为代码大小通常是图片大小的几分之一甚至几十分之一。
9. 通过 Webpack 按需加载代码
懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。
10. 理解性能优化指标
10.1 FCP LCP FID CLS
First Paint
首次绘制(FP)
这个指标用于记录页面第一次绘制像素的时间,如显示页面背景色。FP 不包含默认背景绘制,但包含非默认的背景绘制。
First contentful paint
首次内容绘制 (FCP)
LCP 是指页面开始加载到最大文本块内容或图片显示在页面中的时间。如果 FP 及 FCP 两指标在 2 秒内完成的话我们的页面就算体验优秀。
Largest contentful paint
最大内容绘制 (LCP)
用于记录视窗内最大的元素绘制的时间,该时间会随着页面渲染变化而变化,因为页面中的最大元素在渲染过程中可能会发生改变,另外该指标会在用户第一次交互后停止记录。官方推荐的时间区间,在 2.5 秒内表示体验优秀
Cumulative layout shift
累积布局偏移 (CLS)
累计位移偏移,CLS(Cumulative Layout Shift),记录了页面上非预期的位移波动。页面渲染过程中突然插入一张巨大的图片或者说点击了某个按钮突然动态插入了一块内容等等相当影响用户体验的网站。这个指标就是为这种情况而生的,计算方式为:位移影响的面积 * 位移距离。
10.2 三大核心指标(Core Web Vitals)
Google 在 2020 年五月提出了网站用户体验的三大核心指标
10.2.1 Largest Contentful Paint (LCP)
LCP 代表了页面的速度指标,虽然还存在其他的一些体现速度的指标,但是上文也说过 LCP 能体现的东西更多一些。一是指标实时更新,数据更精确,二是代表着页面最大元素的渲染时间,通常来说页面中最大元素的快速载入能让用户感觉性能还挺好。
那么哪些元素可以被定义为最大元素呢?
<img>
标签<image>
在 svg 中的 image 标签<video>
video 标签- CSS
background url()
加载的图片
参考文档:https://web.dev/articles/lcp?hl=zh-cn
10.2.2 First Input Delay (FID)
FID 代表了页面的交互体验指标,毕竟没有一个用户希望触发交互以后页面的反馈很迟缓,交互响应的快会让用户觉得网页挺流畅。
这个指标其实挺好理解,就是看用户交互事件触发到页面响应中间耗时多少,如果其中有长任务发生的话那么势必会造成响应时间变长。推荐响应用户交互在 100ms 以内。
参考文档:https://web.dev/articles/fid?hl=zh-cn
10.2.3 Cumulative Layout Shift (CLS)
CLS 代表了页面的稳定指标,它能衡量页面是否排版稳定。尤其==在手机上这个指标更为重要==,因为手机屏幕挺小,CLS 值一大的话会让用户觉得页面体验做的很差。CLS 的分数在 0.1 或以下,则为 Good。
参考文档:https://web.dev/articles/cls?hl=zh-cn