前端性能优化

[TOC]

一、资源加载优化

1.减少HTTP请求

2.压缩资源

3.使用CDN

4.图片懒加载(Lazy Loading)

5.预加载与预取

二、渲染性能优化

1.减少重排(Reflow)与重绘(Repaint)

2.使用虚拟DOM(React/Vue)

三、JavaScript优化

1.代码分割(Code Splitting)、按需加载

2.Tree Shaking:移除未使用的代码

3.防抖(Debounce)与节流(Throttle):限制高频事件(如滚动、输入、购物车数量点击)的处理频率

4.避免内存泄漏:及时解绑事件监听器、清理定时器、闭包引用

四、缓存策略

1.HTTP缓存:设置合理的Cache-Control/ETag

2.Service Worker(PWA),实现离线缓存、资源拦截

3.本地存储优化:合理使用localStorage

五、现代Web技术

1.使用现代格式:图片:WebP替代JPEG/PNG。脚本:使用原生ES Module减少打包体积

2.启用HTTP/2或HTTP/3:多路复用、头部压缩等特性提升加载效率

3.字体优化:使用font-display:swap避免文字不可见

六、监控与分析

1.性能指标监控

image-20251224112204656

LCP(最大内容绘制)、CLS(累计布局偏移)、FCP(首个内容绘制)

使用Lighthouse、WebPageTest、Chrome DevTools分析

Web.dev 提高Core Web Vitals 最有效的方法

https://web.dev/articles/top-cwv?hl=zh-cn

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

概念:从用户首次导航到网页,到“视口内可见的最大图片、文本块或视频”渲染出来的事件,这个“视口内可见的最大图片、文本块或视频”元素也被称为LCP元素。

1.LCP元素识别错误

原本LCP元素应该是Banner大图,但是弹窗被当成LCP元素。

原因:

Banner大图实际上是由“CSS渐变背景色+几张小图”组合而成,几张小图都没有订阅弹窗里的背景图大,导致订阅弹窗内的背景图被当成LCP元素。

解决:

1.去除Banner中的CSS背景色并替换成真实的图片(webp)

2.在现有的背景色的情况下,额外加一张透明度为0.001的大图,覆盖整个banner,即可被识别成LCP元素。

2.LCP分段分析

LCP元素需要尽可能小,便于网络加载,因此需要压缩图片。

除此之外,出现LCP分数低的问题,一般是LCP元素“开始加载”的时间点过于靠后,即“资源加载延迟”过久。

利用性能分析工具,可以得到LCP元素的加载情况。

image-20251224114734410

2.CLS(布局偏移量Cumulative Layout Shift)

(布局偏移量,偏移越大分数越低)

概念:网页加载时,若元素造成DOM重排则会增加布局偏移量。某个人元素长宽或者边距突然变大、变小、布局排列方式改变等等,可能会增加布局偏移量。

CLS是跑分成绩中占比较大但是最容易优化的一项,所以尽可能拿满分。
image-20251224104934290

常见的布局偏移场景

(1)没有长宽属性的图片

img图片一定需要设置width、height属性,提前告知浏览器这张图片大小比例。如果缺少这两个属性,图片加载之前渲染的高度会一直是0。

(2)轮播图尚未初始化

一般来讲为了更好性能,我们会让轮播图推迟初始化(网页文档加载之后才初始化轮播图),但轮播图初始化之前,轮播图会处于错位状态。

image-20251224111430674

这种情况下需要为该模块轮播图额外编写“未初始化轮播图时”的样式

代码示例:

Swiper.js 用 :not(.swiper-initialized) 选择器匹配未初始化的轮播图,

1
2
3
4
.section-name .swiper:not(.swiper-initialized) {
/* 编写未初始化轮播图时的样式 */
}
.section-name .swiper:not(.swiper-initialized) .swiper-slide { }

slick.js 用 :not(.slick-initialized) 选择器匹配未初始化的轮播图,

1
2
3
4
.section-name .xxxxxx:not:not(.slick-initialized) {
/* 编写未初始化轮播图时的样式 */
}
.section-name .xxxxxx:not:not(.slick-initialized) .xxx__item{ }

3.CSS或JavaScript动画

产生动画的元素会引起其他元素位移,引起DOM重排。建议使用CSS transform进行变换。

4.推迟加载CSS文件

适当推迟加载CSS有助于网页提升加载速度。但如果推迟的CSS引起布局变化的样式则可能会增加布局偏移。

分析布局偏移

Pagespeed报告->切换到CLS子项报告,标明出现大幅度布局偏移的元素和对应分数。

image-202512241119416241

利用Chrome、Edge等浏览器开发工具(F12)中的性能分析工具。

Layout shifts一栏即为网页加载时间轴中发生的布局偏移,偏移量越大则方块越大

image-20251224111955207

点击防空可以查看发生偏移的DOM元素和导致偏移的因素

image-20251224112053366

image-20251224112113197

3.FCP(首屏加载时间First Contentful Paint)

概念:首屏时间(First Contentful Paint):指浏览器从相应用户输入网络地址,到网页任何一部分内容呈现在屏幕上的时间。一般网页绘制出的第一个内容是导航栏、公告栏,所以优化FCP第一步就是优化导航栏/公告栏之前的代码。

要优化FCP,首先要分析FCP截止前后帧的区别

正常情况:FCP前一帧是全白屏,下一帧只渲染出导航栏,后续帧依次渲染各个模块

image-20251224112743410

异常情况:FCP前一帧是全白屏,下一帧将全部内容一起显示出来,一般是全部内容透明度0直至网页加载完成再解除透明度0所导致的。

image-20251224112733463

分析FCP各部分耗时

在性能分析工具中,找到FCP截止线,展开网络分析哪些资源阻塞了网页加载,展开主要分析哪些脚本函数影响了网页加载。

image-20251224112944084

常用优化手段

1.移除、推迟加载CSS/JS资源

对不常用的CSS、JS,应移除只在需要用到的页面中引入,按需加载。

对于非重要的资源,以及较大的CSS、JS,应推迟加载,如Swiper.js lottie.js plyr.js等。

推迟方式:

外联 CSS 资源添加 media="print" onload="this.media='all'属性

外联JS资源添加defer属性

<script src="{{ 'global.js' | asset_url }}" defer="defer"></script>

通过DOMContentLoaded事件,可以使代码在网页DOM结构加载完成后执行

window.addeventListener('DOMContentLoaded', function() { // ... });

4.INP(交互至绘制延迟Interaction to Next Paint)

交互到下一次绘制时间/交互相应延迟/交互至绘制延迟

1.经常使用yield来拆分长任务

当任务时长超过50ms时,就会成为长任务。冗长任务会造成问题,因为可能会阻止主线程快速响应用户互动。

2.避免不必要的JavaScript

使用Chrome DevTools中覆盖率工具查找脚本未使用代码,通过缩减启动期间所需资源大小,确保网页花在解析和编译代码的时间上更少,从而提供更顺畅的初始用户体验。

给未使用的JavaScript进行代码分块,在使用的时候再进行调用。

3.避免进行大规模渲染更新

在进行大型渲染期间,网站会对用户互动做出相应的速度会变得更慢。

重新整理JavaScript中的DOM读写操作,避免强制布局和布局抖动。

使DOM大小保持小巧。DOM大小与布局工作强度是相关的。当渲染程序必须更新非常大的DOM布局时,重新计算其布局工作量可能会大幅增加。

使用CSS容器来延迟渲染屏幕外DOM的内容。

5.TTFB(首字节时间Time To First Byte)

TTFB = 网络延迟 + 服务器处理时间


具体包括以下阶段:

DNS查询(如未缓存)

TCP连接建立(三次握手)

TLS协商(如果是HTTPS)

发送HTTP请求

服务器处理请求(如查询数据库、渲染页面等等)

服务器开始返回第一个字节


尽可能在距离用户地理位置最近的地方传送内容。

缓存该内容以便在近期再次受到请求时能够快速传送。

最佳方式是使用CDN。CDN会将您的资源分发到全球各地的边缘服务器,从而缩短这些资源通过线路传输到用户的距离。

6.TBT(总阻塞时间Total Blocking Time)

网站应尽力在平均移动硬件上测试将总阻塞时间控制在200毫秒以内。

总阻塞时间 (TBT) 指标用于衡量在首次内容渲染 (FCP) 之后,主线程处于阻塞状态的总时长,在此期间,主线程处于阻塞状态的时间够长,足以阻止输入响应。

只要有长任务(在主线程上运行超过 50 毫秒的任务),主线程就会被视为“阻塞”。之所以说主线程被“阻塞”,是因为浏览器无法中断正在进行的任务。因此,如果用户在长时间任务执行到一半时确实与网页互动,浏览器必须等待任务完成才能做出响应。

如何缩短TBT

一般来说,提高网站的TBT需要减少阻塞脚本的数量,这意味着要么优化脚本以减少阻塞,要么减少脚本的总量。

缩短JavaScript的执行时间,最大限度减少主线程工作,保持较低的请求数量和较小的传输大小。

2.真实用户监控(RUM)

收集真实设备上的性能数据(如通过Sentry、Google Analytics)

七、框架特定优化(以React为例)

使用React.memo、useMemo、useCallback 避免重复渲染

使用Suspense + lazy实现组件懒加载

避免在render中创建新对象/函数