一直想写篇前端性能相关的总结,个人觉得这块的内容会比较分散,面试的时候问起来,也不容易有一个清晰的框架,但是平时的习惯是想起来什么就写什么,所以攒了好久的内容只能躺在一堆笔记草稿里面;

---🚩🚩正文分割线🚩🚩---

按页面加载链路分类,从下面几个方面开始

  • 首屏加载
  • 代码优化
  • 构建工具

首屏加载

这部分其实就是把从获取资源到页面呈现中可以优化的点提取出来

1. DNS预解析

不需要用户点击链接就在后台解析,在head中添加

<link rel="dns-prefetch" href="//example.com">

但是要注意会增加一定的网络请求和带宽消耗,非必要域名谨慎使用

2. 开启HTTP2

首先说下相对于HTTP1的优势

  • 多路复用,能够在单个TCP连接上同时传输多个请求和响应;HTTP1.1有一个可选的Pipelining技术,但它是按照顺序处理响应的,后发的请求可能被先发的请求阻塞,所以很多浏览器默认不开启。
  • 首部压缩,使用HPACK算法对请求和响应头部进行压缩,减少了首部大小,节省了带宽。而在HTTP/1.x中,每次请求都需要发送完整的头部信息,很容易造成不必要的带宽浪费。
  • 服务器推送,服务端可以在发送页面HTML,也就是客户端请求对应HTML页面时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应。
  • 二进制分帧,使用二进制协议对数据进行分帧传输。二进制协议更高效,减少了解析数据的开销,并提高了传输速度。

    在nginx中开启HTTP2

# 修改nginx.conf中的配置
server {
    listen 443 ssl http2;
    server_name example.com;

    # SSL证书和密钥路径
    ssl_certificate /path/to/ssl/cert;
    ssl_certificate_key /path/to/ssl/key;

    # 其他配置项
}

3. 资源的预加载

这个其实是安排资源以更高的优先级进行下载和缓存,更详细的可以看看MDN文档

<link rel="preload" href="styles.css" as="style">
<link rel="preload" href="main.js" as="script" />

4. 动态创建加载脚本

不管是js还是css,在下载过程中其实都是会阻塞页面的,动态加载会在下载资源的同时,也不影响后续代码的执行

const script = document.createElement("script");
script.src = "myscript.js";
document.body.appendChild(script);

5. 同构渲染

其实就是服务端渲染(SSR)+ 客户端渲染(CSR),服务端渲染的升级版,像现在的Nuxt.js或者Next.js就可以实现;

引用《vue.js设计与实现》的对比来更直观的了解同构渲染的特点

CSRSSR同构
SEO不友好友好友好
白屏问题
占用服务器资源
用户体验

6. 可见性优化

这部分主要是针对非可视区域进行延迟加载来减少首屏执行的逻辑

非可视区域

  • 延迟接口请求,使用setTimeout或者then函数来置后加载时机;
  • 图片懒加载,使用IntersectionObserver实现可视区域判断;

虚拟滚动

只加载上下及当前页的数据;可以通过滚动时分页或者vue-virtual-scroll-listreact-virtualized一类的插件实现;

7. 针对白屏/抖动

加载过程中无法避免的会有短暂白屏,

  • 骨架屏,可以选择固定灰色块,或者计算页面元素宽高生成灰色快;
  • loading,加入比较有意思的loading动画;
  • 定义宽高,图片或者接口数据在渲染到页面之后,会撑开所在的元素,就造成页面抖动,设置好盒子或者图片的宽高或者设置个占位;
  • 字体闪烁,加载字体且生效之前的闪烁,通过压缩字体减小资源体积,设置font-display:block来解决加载过程中的字体样式异常;

8. 静态资源

  • 合理使用协商缓存和强缓存及本地存储
  • 使用字体图标代替图片图标
  • 使用webp
  • 图片压缩
  • 使用cdn

代码优化

这部分只是列出来可优化点,感兴趣可以去搜索相关实现,建议只有出现明确的性能问题存在时,才进行优化

1. JS

  • 使用scriptasyncdefer属性避免阻塞;
  • service worker,拦截网络请求,灵活的判断是否需要缓存资源;
  • Web Worker,创建一个新的线程,在一个独立的js环境中执行逻辑,不会阻塞后续逻辑的执行,针对耗时的计算任务或者执行时间比较久的逻辑处理;
  • 批量请求及事件任务切片;
  • 节流和防抖;
  • 事件委托;
  • 及时销毁闭包及定时器;
  • 缓存变量及dom属性;
  • 变量作用域的合理声明;

2. CSS

  • 回流属性放在一块集中修改;
  • 避免选择器嵌套过深;
  • CSS动画代替JS动画;
  • 使用伪元素简化html结构,如:before代替div
  • 开启GPU加速,这个非必要不推荐开启;
  • 减少CSS类名查找范围,浏览器解析CSS遵循的是从右到左的查找规范,先找.b再找.a,将.wrap .a .b改为wrap .b

3. Vue

  • v-showv-if的合理使用,频繁更新显示状态使用v-show
  • 使用keep-alivev-once减少多余的更新渲染;
  • 通过Object.freeze移除双向绑定,减少不必要的数据监听;
  • 避免template中使用复杂的表达式;

4. React

  • memo,减少子组件的重复渲染,简单组件不会有太大的效果,并且会加大内存消耗;
  • useMemo,相当于Vue中的computed函数,所设置的依赖没有变化时,就会返回上一次的计算结果;
  • useCallback,避免重复创建函数;
  • 组件卸载清理,Class组件:componentWillUnmount,Function 组件:useEffect return
  • 使用React Fragment,减少额外节点的渲染;

构建工具

目前基本上使用vite,这里主要针对vite优化,webpack就简单带过

1. webpack

  • 指定模块解析范围,设置解析文件类型范围
  • webpack打包/构建缓存hard-source-webpack-plugin
  • 资源的压缩,拆分、第三方包的提取合并(config配置optimization.splitChunks);

2. 摇树优化

就是在保证代码运行结果不变的前提下,去除无用的代码;其实Rollup会默认开启摇树优化,但需要是ES6 module模块,第三方包尽管可能使用esm版本,本身体积会更小,而且能有更好的压缩效果

import { cloneDeep } from 'lodash'
// 改为
import { cloneDeep } from 'lodash-es'

const obj = cloneDeep({}) // 如果这行被注释,vite就不会再引入lodash包

删除线上的consoledebugger,这个根据项目需求决定是否需要配置

  {
    esbuild: {
      drop: ['console', 'debugger'],
    }
  }

3. gzip压缩

这个就是在客户端进行文件压缩,服务端直接调用

import viteCompression from 'vite-plugin-compression';

viteCompression({
    verbose: true,
    disable: false, // 不禁⽤压缩
    deleteOriginFile: false, // 压缩后是否删除原⽂件
    threshold: 10240, // 压缩前最⼩⽂件⼤⼩
    algorithm: 'gzip', // 压缩算法
    ext: '.gz', // ⽂件类型
}),

nginx配置静态gzip压缩,会直接读取文件夹中.gz文件

# 修改nginx.conf中的配置
http {
 gzip_static on;
}

Content-Encodinggzip就表示设置成功了

4. 图片压缩

import viteImagemin from 'vite-plugin-imagemin'

viteImagemin({
    gifsicle: { // gif图片压缩
      optimizationLevel: 3, // 选择1到3之间的优化级别
      interlaced: false, // 隔行扫描gif进行渐进式渲染
      // colors: 2 // 将每个输出GIF中不同颜色的数量减少到num或更少。数字必须介于2和256之间。
    },
    optipng: { // png
      optimizationLevel: 7, // 选择0到7之间的优化级别
    },
    mozjpeg: {// jpeg
      quality: 20, // 压缩质量,范围从0(最差)到100(最佳)。
    },
    pngquant: {// png
      quality: [0.8, 0.9], // Min和max是介于0(最差)到1(最佳)之间的数字,类似于JPEG。达到或超过最高质量所需的最少量的颜色。如果转换导致质量低于最低质量,图像将不会被保存。
      speed: 4, // 压缩速度,1(强力)到11(最快)
    },
    svgo: { // svg压缩
      plugins: [
        {
          name: 'removeViewBox',
        },
        {
          name: 'removeEmptyAttrs',
          active: false,
        },
      ],
    },
  })

5. 依赖分析

基本上都是用这种方式来查找不必要的依赖引用来减小包体积

import { visualizer } from 'rollup-plugin-visualizer';

const command = process.env.npm_lifecycle_event

 {
  plugins: [
    command === 'report' ? 
    visualizer({ open: true, brotliSize: true, filename: 'report.html' })
    : null
  ]
 }

最后

从整体架构方面还可以做下面几件事;

  • 组件增加权重,针对单个组件,给组件添加权重值,针对权重大的组件优先展示;
  • 按机型加载资源,根据当前系统版本、机型配置做不同的资源加载,动画交互降级;
  • 微前端 拆分应用,剥离业务,减少业务之间的关联影响,使用micro-app或者qiankun

最后优化是有成本的,也需要根据场景决定是否进行优化

传送门

万字长文:分享前端性能优化知识体系


稀饭52
21 声望4 粉丝

愿将来的你不会讨厌现在的自己