从输入 URL 到页面加载完成,经历了下面几个步骤,针对这些步骤逐个优化:

优化点 问题 解决
DNS解析 减少DNS解析次数,把解析前置? DNS缓存和DNS prefetch
TCP连接 TCP三次握手,时间太久? 长连接,预连接,接入SPDY协议
HTTP请求 HTTP请求怎么优化? 减少请求次数和请求体积,CDN等网络层面的性能优化
浏览器渲染 拿到内容后,如何加快响应速度? 资源加载优化、服务端渲染、浏览器缓存机制、DOM树构建、网页排版和渲染、回流和重绘、DOM操作合理规避等问题。

总的来说,前端性能优化可以分为 网络层面渲染层面 两个大点。
终极方案: 服务器渲染SSR

1. 网络层面

1.1 HTTP

  1. 浏览器缓存

    • 对于不需要缓存的资源,使用Cache-control: no-store
    • 对于频繁变动的资源,使用Cache-control: no-cache并配合ETag,表示该资源已被缓存,但是每次都会发送请求询问资源是否需要更新
    • 对于代码文件,通常使用Cache-control: max-age=31536000强缓存,然后对文件进行指纹处理,一旦文件名变动就会立刻下载新的文件
  2. 文件压缩
    在request header中加上accept-encoding:gzip

  3. 资源部署

    • 多个地址: HTML部署到www.example.org,而把js, css静态资源分离到static.example.org。最大化并行下载(浏览器限制6个)
    • cdn加速

1.2 Webpack优化

  • 分包
    • 懒加载
    • 减少体积过大的模块
  • 压缩,tree shaking,
  • 图片优化
    • 图片压缩: 适当缩小尺寸、分辨率、选择高压缩率的格式,有损格式应该用 JPEG,无损格式应该用 Webp 格式。合并照片
    • 小图片使用base64
    • 图标使用svg,使用svgo压缩
    • 代码绘制代替图片:一些可以用代码画出来的素材,渐变大背景图

2. 渲染层面

  1. 渲染优化
    • 减少页面重排、重绘:回流、重绘、合成
    • 少用高性能CSS属性:浮动、定位
    • 选择器优化:避免层级过深,尽量不适用tag选择器,因为CSS选择符是从右到左进行匹配的,会匹配到很多元素
  2. 事件委托:避免太多事件处理器被添加到DOM元素上
  3. 虚拟列表:避免页面元素太多造成卡顿
  4. 内存管理:及时解除事件监听、清除定时器,避免内存泄漏
  5. ServiceWorker:开销大的操作新开一个线程操作,避免造成卡顿
  6. 事件防抖和节流
    • 防抖:事件被触发n秒再执行,如果在这n秒内又被触发,则重新计时
    • 节流:规定在一定时间内只能触发一次函数
  7. 组件级优化
    • 减少使用内联函数的写法v-click="() => {}",减少不必要的渲染
    • useMemo, useCallback, v-once/v-memo手动标记缓存组件和计算结果,也是为了减少不必要的渲染
    • vue3中使用shallowRef代替放弃深层响应性,只有.value的访问被追踪。浅层ref可避免大型数据的响应性开销
  8. 减少http请求: vuex, redux保留全局数据
  9. 组件懒加载: () => import()异步加载组件

10.用ImmerJS: 使用Proxy实现了数据拷贝,数据修改开销更小

3. 性能监控指标

指标 目标值 测量工具
首屏加载时间(FCP) <1.5 秒 Lighthouse
最大内容绘制(LCP) <2.5 秒 Web Vitals
输入延迟(INP) <200 毫秒 Chrome DevTools
JavaScript 执行时间 <300 毫秒 Performance 面板

参考资料