前端极致性能优化手册大全

5,295 阅读4分钟

原文链接

前端优化之路必不可少的知识点。

  • 浏览器输入url到页面的展现,具体发生了些什么,前端能做哪些优化
  1. DNS 递归查询解析 —— DNS优化prefetch;
  2. TCP 三次握手、四次挥手 —— http1.0/1.1/2.0 的区别,http/s的区别;
  3. http 缓存 —— 304 与 CDN;
  4. 浏览器渲染机制 —— CSS、JS顺序的重要性,@import的损耗,防抖、节流、重排、重绘,GPU加速等;
  5. 如何优化JS主线程 —— web worker,任务分片
  6. ...
  • 图片你优化了吗,雪碧图、webp、svg;
  • webpack 等打包优化
  • 运维的基本知识 nginx

本文按一定顺序总结与前端性能优化的基本点,大家可以按步骤逐一检查自己的项目,找出性能的瓶颈。如有错误遗漏欢迎补充纠正。

文章有些原理细节都在参考文章中,价值较高建议读一读。

webpack

默认的 webpack4 很多优化内部已经做到很好了,但无法满足所有的业务场景, 如果发现开发时打包慢、打包体积太大,这是你就要审视下配置了。

代码分块分析插件 webpack-bundle-analyzer

npm i webpack-bundle-analyzer -D
  • 修改 webpack.config.js
// 在头部添加
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

// 在plugins: [] 中新增配置如下
new BundleAnalyzerPlugin({
  analyzerMode: 'server',
  analyzerHost: '127.0.0.1',
  analyzerPort: 8000,
  reportFilename: 'report.html',
  defaultSizes: 'parsed',
  openAnalyzer: true,
  generateStatsFile: false,
  statsFilename: 'stats.json',
  statsOptions: null,
  logLevel: 'info'
})
  • 启动本地开发服务,浏览器中打开 http://127.0.0.1:8000

    webpack-bundle-analyzer

  • webpack4 默认代码分割策略

  1. 新的 chunk 是否被共享或者是来自 node_modules 的模块
  2. 新的 chunk 体积在压缩之前是否大于 30kb
  3. 按需加载 chunk 的并发请求数量小于等于 5 个
  4. 页面初始加载时的并发请求数量小于等于 3 个

比如,由于业务中频繁 antd 中的UI组件,但他们都小于 30kb 不会被独立打包,导致过多重复的代码打入不同 chunk 中。 这时根据实际业务情况,默认的配置就不满足需求了。修改策略:

  1. react 全家桶和状态管理一个 vendor
  2. antd 相关的一个 lib
  3. node_modules 里的打成 common
// 默认配置
splitChunks: {
  chunks: 'all',
  name: false,
}

// 修改后的配置
splitChunks: {
  chunks: 'all',
  name: false,
  cacheGroups: {
    vendor: {
      name: 'vendor',
      test: module = >/(react|react-dom|react-router-dom|mobx|mobx-react)/.test(module.context),
      chunks: 'initial',
      priority: 11
    },
    libs: {
      name: 'libs',
      test: (module) = >/(moment|antd|lodash)/.test(module.context),
      chunks: 'all',
      priority: 14
    },
    common: {
      chunks: "async",
      test: /[\\/] node_modules[\\ / ] / ,
      name: "common",
      minChunks: 3,
      maxAsyncRequests: 5,
      maxSize: 1000000,
      priority: 10
    }
  }
}

结论:

  • 优化前体积为56MB(去除sourceMap)
  • 优化后体积为36MB(保留sourceMap,去除sourceMap大约在7.625MB)

glob 和 purgecss-webpack-plugin 去除无用CSS

npm i glob purgecss-webpack-plugin -D
// 在webpack.config.js中的plugins: []中添加.
// 需要注意的是paths一定要是绝对路径,比如antd的样式不在src目录下就需要写成一个数组,要不然antd的样式就会不见了
new purgecssWebpackPlugin({
  paths: glob.sync(`${paths.appSrc}/**/*`, { nodir: true })
})

结论:CSS资源减小很多

一番操作下来

  • 目前资源已经减小到7.25M左右;
  • 打包速度由7.5分钟减少到2.5分钟,效率极大提升。

图片

webp

webp 是一种新式图片格式,在保证品质的同时提供无损和有损压缩。 webp 对于图片较多的站点是必不可少的优化手段,一般 CDN 都有提供转换服务。

  • 某宝大规模使用

    webp

  • 优点:在同等品质下,无损图片比 png 减少 26% 的大小,有损下比 jpeg25-34%

  • 缺点:有的浏览器不兼容,需要做兼容,以下是官方提供

// check_webp_feature:
//   'feature' can be one of 'lossy', 'lossless', 'alpha' or 'animation'.
//   'callback(feature, result)' will be passed back the detection result (in an asynchronous way!)
function check_webp_feature(feature, callback) {
    var kTestImages = {
        lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
        lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
        alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
        animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"
    };
    var img = new Image();
    img.onload = function () {
        var result = (img.width > 0) && (img.height > 0);
        callback(feature, result);
    };
    img.onerror = function () {
        callback(feature, false);
    };
    img.src = "data:image/webp;base64," + kTestImages[feature];
}
  • 根据客户端上报的请求头 accept: image/webp,服务端进行判断,支持则返回 webp 不支持则原图。

雪碧图

多个图片拼成一个图片,在利用 background-position 定位。 减少 http 请求。

HTTP2 可以解决线头阻塞问题。

iconfont

svg 版雪碧图

base64

// 字符转
window.btoa('str');
// canvas 转
canvas.toDataURL();
// 图片转
const reader = new FileReader();
const imgUrlBase64 = reader.readAsDataURL(file);
// webpack 打包转
// url-loader
  • 优点:便于存储在html、js中,减少http请求个数。
  • 缺点:文件尺寸增大 30% 左右。

适用于少量小图的场景。

缓存

DNS缓存

查找过程

  1. 先浏览器 DNS 缓存
  2. 查找 hosts 文件域名 IP 映射(你知道背墙DNS污染但没封的IP,可以设置hosts文件访问)
  3. 查找本地 DNS解析器(路由) 缓存
  4. 根DNS服务器 -> 顶级.com -> 二级域名服务器xx.com -> 主机 www.xx.com
  • 可以看出某些 DNS 解析占大头时间,优化还是很有必要的

    DNS

  • dns-prefetch,例如访问某宝首页,猜测你接下来要访问某些域名,提前去解析。以节省下个页面的 DNS 查询。 不过大量不必要的预获取,对公共网络资源造成较大浪费。

    dns-prefetch

  • 优化后

    dns-better

http 缓存

http 缓存

简单提下,对于现在 SPA 项目,一般静态资源放在 CDN 上, 对经常变动入口文件index.html 设置强制检验过期 Cache-Control: no-cahce 或直接不缓存。 其他 hash 命名的资源直接设置长缓存(max-age: 一年半载)。

具体详情已在另一篇文字阐释,文末链接。

CDN(Content Delivery Network) 内容分发网络

优点:

  1. 资源文件多处备份,就近原则,网络离用户最近的服务器提供服务,速度快、安全性高;
  2. 带宽贵啊,大量的用户访问,不上 CDN ,网站很卡或崩溃。

本地缓存 localStorage、sessionStorage、cookie

  • storage
  1. localStorage 一直存在浏览器中,要么用户删除或浏览器缓存策略剔除
  2. sessionStorage 页面关闭消失

优点:可以存储较大的数据 Chrome 5M

  • cookie

相比 storage

  1. 优点:可以设置失效时间
  2. 缺点:存储量较小,http1.x 每次会上报给服务器,造成网络浪费。

建议:对服务器安全数据设置 http-only,能少用尽量少用,只用来与服务器进行状态维护和用户识别。

浏览器渲染

CSS

  • 减少 @import 的使用,浏览器解析 html 会优化嗅探获并发获取文件,如果使用 @import 需要下载解析了当前 CSS 文件,才能下载。
  • CSS 权重较高,应该优先下载解析。

脚本 defer、async

只对外联脚本有效。众所周知,脚本解析会阻塞 DOM 解析,这两个属性就是为了解决这个问题。

  • defer 下载时不阻塞 HTML 解析成 DOM,下载完成会等待 DOM 构建完毕且在 DOMContentLoaded 事件触发之前执行,多个 defer 脚本保证脚本执行顺序。

  • async 下载时不阻塞 HTML 解析成 DOM,下载完毕后尽量安排JS执行。意思说执行时间不确定,早下载早执行。如果 DOM 未构建完,脚本可能会阻塞DOM构建。

  • 例如某宝大量在头部使用 async

    async

  • 但不是很理解是,按原理 async 应该用在对 DOM构建 和脚本顺序无依赖的场景,而且下载太快还可能阻塞 DOM构建。感觉 defer 更合适。

防抖、节流

在其他文章有详细说明,在此不再赘述,请看参考索引。

防止强制布局

  • 主要是在避免在循环中又读又写样式
    FSL

GPU 加速

  1. will-change: transform
  2. transform: translateZ(0)

会单独把元素提升层级交给 GPU 渲染,适合一些 Animation 动画。

requestAnimation, requestIdelCallback

  • google 文档上有很多探讨,检测计算长任务的新 api 进展。
  • Facebook 最新 react 中的 fiber 调度,就使用了 requestAnimation, requestIdelCallback 来 进行长任务的时间切片,避免以前深 DOM 树更新产生长耗时甚至抖动。
    main-thread

web worker

对于需要大量计算会占用渲染主线程,适合放到 web worker 来执行。

服务器

http2

http1.1 对比 http 1.0 主要进步有

  1. 缓存处理的增强,如 Etag
  2. 加入 range头,响应码206(Partial Content) 支持断点续传
  3. 加入 host 头,多个域名可以绑定一个 IP
  4. 响应头 Connection: keep-alive,长连接,客户端与同一个主机通信不必多次 三次握手

https 与 http

  1. https 需要申请 CA 证书,要钱;https 在 http 上多了层安全协议 SSL/TLS
  2. 客户端进行 CA 认证,连接需要非对称加密(耗时),传输数据使用对称加密;
  3. 防止运营商 http 劫持,插广告等。http 端口 80,https 443

http2 对比 http1.x

  1. header压缩,例如后者每次传输数据都得带很多相同的头部信息,http2 会压缩头部且避免重复头部重传;
  2. 新的二进制格式,后者是基于文本协议解析,前者基于01串,方便且健壮;
  3. 多路复用,注意与 keep-alive 区分。前者是连接共享,每个请求有个唯一 id 来确认归属,多个请求可以同时相互混杂。 而后者减少了握手保持长联,会影响服务器性能,先进先出需要等前一个请求发完才能进行下一个,造成线头阻塞。客户端一般会限制一个页面与不同服务器同时http连接上限;
  4. 服务器推送,http1.x服务器只能被动接收请求发送资源,HTTP2可以主动推送。

http2 可以提升传输效率。nginx 有必要做好 http2 的升级和降级处理。

gzip

服务器开启压缩,文本类型的文件能够减少网络传输。 特别是文件较大且重复率高的文本压缩效果更明显。

  • 如图 index.html 文件压缩 (383-230)/383=39.95%
    gzip
  1. Chrome request headers 告诉服务器支持的压缩算法:Accept-Encoding: gzip, deflate, br
  2. 服务器响应使用的压缩算法 response headers:Content-Encoding: gzip
  • nginx 开启
gzip on;
// 不压缩临界值,大于1K的才压缩,一般不用改
gzip_min_length 1k;
// 压缩级别,1-10,数字越大压缩的越细,时间也越长
gzip_comp_level 2;
// 进行压缩的文件类型
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
// ie兼容性不好所以放弃
gzip_disable "MSIE [1-6]\.";

compression-webpack-plugin 配合 gzip

npm i compression-webpack-plugin -D
const CompressionWebpackPlugin = require('compression-webpack-plugin');

// 在webpack.config.js中的plugins: []中添加.
new CompressionWebpackPlugin({
  asset: '[path].gz[query]', // 目标文件名
  algorithm: 'gzip', // 使用gzip压缩
  test: /\.(js|css)$/, // 压缩 js 与 css
  threshold: 10240, // 资源文件大于10240B=10kB时会被压缩
  minRatio: 0.8, // 最小压缩比达到0.8时才会被压缩
})
  • 优点 nginx 开启 gzip,发现有压缩好的 gzip 压缩文件,就会直接使用,减少服务器 cpu 的资源的使用。
  • 缺点 打包时间就会拉长。现在静态资源一般会上CDNgzip 是CDN服务器基本的服务, 需要去节省本就花了钱买的服务器资源,而增加打包的时间吗?

es6、动态使用POLYFILL

webpack 默认支持 es6 的新特性 tree-shaking,可以摇掉不用的代码,且新api的性能很高。 推荐全面使用。

// 会根据ua返回不同的内容
https://polyfill.io/v3/polyfill.min.js
方案 优点 缺点
babel-polyfill React 官方推荐 体积200kb
babel-plugin-transform-runtime 体积小 不能poyfill原型上的方法
polyfill-service 动态根据ui加载 兼容性,国内部分浏览器有问题

结论:可以减少资源大小,但依赖外部服务,自建麻烦的话,放弃。

参考

  1. 从优化到面试装逼指南——网络系列

浏览器渲染

  1. 防抖debounce 与 节流throttle
  2. 如何构建 60FPS 应用
  3. 一帧剖析
  4. 性能优化——关键路径渲染优化

webpack

  1. 手摸手,带你用合理的姿势使用webpack4(下)

http相关

  1. HTTP 缓存
  2. TCP的三次握手与四次挥手(详解+动图)
  3. HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
  4. nginx 完整配置
  5. DNS递归查询与迭代查询

iconfont

  1. 使用方法
  2. 相关文章