前端性能优化的解决方案
🚀

前端性能优化的解决方案

Property
FrontEnd
Year
2022
Author
Tweet

1. 渲染优化

与浏览器为友,共进退

浏览器渲染原理和关键渲染路径

notion image
  1. JavaScript
用于触发视觉变化,包括脚本的触发,CSS 的变化,animation 的变化
  1. Style
浏览器对样式进行重新计算
  1. Layout
布局(回流):将元素按照样式布局到页面相应的位置
  1. Paint
绘制:开始绘制
  1. Composite
合成,将不同的层绘制在对应的位置再进行合成
 
  • 构建 DOM 对象
HTML DOM
  • 构建 CSSOM 对象
CSSCSSOM
 

回流(reflow

 
页面第一次加载 Layout 这一步是叫做布局,后面的操作导致的重新布局叫做回流
影响回流的操作
  1. 添加/删除元素
  1. display: none
  1. 移动元素位置
  1. 操作 styles
  1. offsetLeftscrollTopclientWidth
  1. 修改浏览器大小,字体大小
避免 Layout thrashing
  • 避免回流
    • 比如改变元素的位置,不要直接修改元素的位置,可以使用transformtranslate来改变位移
  • 读写分离
    • 读写的批量操作(虚拟DOM)
    • 使用 FastDom
 

重绘(repaint

减少重绘的方式
  • 使用 transform opacity

复合线程(compositor thread)与图层(layers

复合线程做了什么
  1. 将页面拆分图层进行绘制再进行复合
    1. 浏览器按照某种规则进行拆分
    2. 可手动拆分
  1. 利用 DevTools 了解网页的图层拆分情况
  1. 哪些样式仅影响复合(不会影响 Layout Paint
    1. position
      1. transform: translate
    2. Scale
      1. transfrom: scale
    3. Rotation
      1. transform: rotate
    4. Opacity
      1. opacity: 0...1
         

高频事件防抖

React 时间调度实现

 

2. 代码优化

如何写出真正高性能的代码

JS开销和如何缩短解析时间

开销在哪里
  1. 加载
  1. 解析和编译
  1. 执行
解决方案
  1. Code splitting 代码拆分,按需加载
  1. Tree shaking 代码减重
  1. 避免长任务
  1. 避免超过 1kb 的行间脚本
  1. 使用 rAFrIC 进行时间调度
  1. 可见不可交互 vs 最小可交互资源集
notion image

配合V8 有效优化代码

notion image
源码 ⇒ 抽象语法树 ⇒ 字节码Bytecode ⇒ 机器码
  • 编译过程会进行优化
  • 运行时可能反生反优化
  • 脚本流
  • 字节码缓存
  • 懒解析

函数优化

函数的解析方式
  • lazy parsing 懒解析 vs eager parsing 饥饿解析
  • Optimize.js 优化初次加载时间(Webpack 已经配备)

对象优化

对象优化的方式
  1. 以相同顺序初始化对象成员,避免隐藏类的调整
  1. 避免元素类型的转换
  1. 实例化后避免添加新属性
  1. 避免读取超过数组的长度
    1. function foo(array) { for(let i = 0; i <= array.length; i++) { // 越界比较 if(array[i] > 1000) { // 1. 造成 undefined 跟数字进行比较 2. 沿原型链查找 console.log(array[i]) // 业务上无效、出错 } } } const array = [10, 100, 1000]
  1. 尽量使用 Array 代替 array-like 对象
    1. 将类数组转换为 Array 后再使用 数组的方法 效率会更高
      // arrobj 为类数组 Array.prototype.forEach.call(arrobj, (value, index) => { console.log(`${index}: ${value}`) }) const arr = Array.prototype.slice.call(arrobj, 0); arr.forEach(() => {}) // 下面先转换为数组的运行速率会更快 // 转换的代码比优化影响小

HTML优化

  1. 减少iFrames使用
  1. 压缩空白符
  1. 避免节点深层级嵌套
  1. 避免 table 布局
  1. 删除注释
  1. CSS & JavaScript 尽量外链
  1. 删除元素默认属性

CSS优化

  1. 降低 CSS 对渲染的阻塞
  1. 利用 GPU 进行完成动画
  1. 使用 contain 属性
  1. 使用 font-display 属性

3. 资源优化

经典性能优化解决方案

资源的压缩与合并

  1. HTML压缩
    1. 使用在线工具进行压缩
    2. 使用 html-minifiernpm 工具
  1. CSS 压缩
    1. 使用在线工具进行压缩
    2. 使用 clean-cssnpm 工具
  1. JS压缩与混淆
    1. 使用在线工具进行压缩
    2. 使用 webpack 对 JS 在构建时压缩
  1. CSS JS 文件合并
    1. 文件小的可以进行合并
    2. 无冲突时,服务相同的模块可以进行合并
    3. 只是优化加载不建议

图片格式优化

  1. JPEG/JPG 的优点
  1. PNG 的优点
  1. WEBP的优点

图片加载优化

  1. 懒加载
    1. 第三方图片懒加载方案
    2. 原生的图片的懒加载方案
    3. <img loading="lazy" />
  1. 使用响应式图片
    1. Srcset 属性的使用
    2. Sizes 属性的使用
    3. picture 的使用

字体优化

4. 构建优化

webpack 构建优化

webpack 的优化配置

Tree Shaking
  1. 上下文未用到的代码(dead code
  1. 基于 ES6 import export
  1. 局限性
    1. // package.json "sideEffects": [ ... ]
JS压缩
  1. Webpack 4 后引入的 uglifyjs-webpack-plugin
  1. 支持 ES6 替换为 terser-webpack-plugin
  1. 减少 JS 文件体积
作用域提升
  1. 代码体积减少(依赖关系进行合并)
  1. 提高执行效率
  1. 注意 babelmodules 的配置
babel7 优化配置
  1. 在需要的地方引入 polyfill
  1. 辅助函数的按需引入
  1. 根据目标浏览器按需转换代码

webpack 的依赖优化

noparse
  1. 提高构建速度
  1. 直接通知 webpack 忽略较大的库
    1. 被忽略的库不能有importrequiredefine的引入方式
DllPlugin
  1. 避免打包时对不变的库重复构建(如 reactreact-dom

基于 webpack 的代码拆分

代码拆分做什么
  1. 把单个文件拆分成若干个bundles / chunks
  1. 缩短首屏加载时间
拆分的方式
  1. 手动定义入口
  1. splitChunks 提取共有代码,拆分业务代码与第三方库

基于 webpack 的资源压缩

minification
  1. Terser 压缩 JS
  1. mini-css-extra-plugins 压缩CSS
  1. HtmlWebpackPlugin - minify 压缩HTML

基于 webpack 的资源持久化缓存

  1. 每个打包的资源文件有唯一的 hash 值
  1. 修改后只有受影响的文件的 hash 值发生变化
  1. 充分利用浏览器缓存

基于 webpack 的应用大小监测与分析

  1. 静态代码分析与可视化图
  1. webpack-bundle-analyzer 进行体积分析
  1. speed-measure-webpack-plugin 速度分析

React按需加载的实现方式

  1. React router 基于 webpack 动态引入
  1. 使用 Reloadable 高级组件

5. 传输加载优化

启用压缩GZip

  • 对传输资源进行压缩,可高达90%
  • 使用 Nginx 配置 GZip

启用 keep Alive

HTTP资源缓存

service worker

HTTP 2

SSR

6. 前沿优化解决方案

移动端图标使用SVG

使用FlexBox优化布局

优化资源加载顺序

预渲染页面

windowing提高列表性能

使用骨架组件减少布局移动