前端常见性能优化手段

728 阅读6分钟

一直以前,前端性能优化都是一个面试必问的题目,面试官可能通过以下几个问题来进行提问:

  1. 从输入一个URL到最终页面渲染经历了什么?
  2. 项目中对webpack做了哪些配置上的优化?
  3. 项目中做过性能优化吗?
  4. 知道哪些性能优化的方式?

本篇文章就会对性能优化进行一个系统性的总结和实践。

开发构建阶段

提升打包速度

  1. 使用高版本的webpack
  2. 多进程打包
    • thread-loader (官方推荐)
    • paraller-webpack
    • HappyPack (已经不在维护)

这里简单介绍下thread-loader的使用,安装好thread-loader后,在webpack rules中配置

    {
        test: /.js$/,
        include: path.resolve('src'),
        use: [
            {
                 loader: 'thread-loader',
                 options: {
                     workers: 3
                 }
            }
        ]
    }

注:Vue cli默认是配置了parallel属性,在cpu内核大于1时,对Babel和TS使用thread-loader

减少打包体积

打包体积分析插件webpack-bundle-analyzer,根据生成的界面查看哪部分体积过大,再针对性优化

  1. 开启gzip
    new CompressionWebpackPlugin({
        test: /\.(js|css|json|txt|html|svg)(\?.*)?$/i,
        deleteOriginalAssets: true, // 是否删除源文件,
        algorithm: 'gzip',
        // filename: '[path][base].gz',
        // threshold: 0,
        minRatio: 2

    }),
  1. 压缩图片 image-webpack-loader
// vue.config.js中的配置如下
   config.module
    .rule('image')
    .test(/\.(png|jpe?g|gif)(\?.*)?$/)
    .use('image-webpack-loader')
    .loader('image-webpack-loader')
    .options({
        // 此处为ture的时候不会启用压缩处理,目的是为了开发模式下调试速度更快
        disable: process.env.NODE_ENV === 'development'
    })
    .end()
  1. 通过IgnorePlugin忽略部分内容的打包
// 忽略moment 国际化内容的打包
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
  1. 合理划分chunks

通过SplitChunksPlugin进行公共脚本分离

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async', // async initial all
      minSize: 20000,
      minRemainingSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      enforceSizeThreshold: 50000,
      cacheGroups: {
        // 提供公共脚本
        commons:{
             name:'commons',
             chunks:'all',
             minChunks:2
         },
        // 项目包生成vendor chunk
        defaultVendors: {
          test: /[\/]node_modules[\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
      },
    },
  },
};
  1. 基础库分离

将vue vue-router等资源通过CDN引入,不打入bundle中

利用缓存

  • babel-loader开启缓存
    options: {
        cacheDirectory: true
    }
  • cache-loader 或者 hard-source-webpack-plugin提升二次构建速度

缩小范围

  1. 通过exclude进行目录排除
  2. 通过path.resolve()
  3. 合理利用alias

资源加载阶段

  1. 图片格式的合理使用
  • PNG:无损压缩,支持透明,简单的图片尺寸较小,但是如果色彩丰富的图片存储为png,体积较大,不支持动画,一般用于logo icon等

  • GIF:支持动画,支持透明,但不支持半透明,边缘有杂边,文件小,只支持256中颜色。适合用于色彩简单的logo、icon、动图

  • JPG:有损压缩 体积小,不支持透明 适合色彩丰富的图片

  • webp:文件小,支持有损和无损压缩,支持动画、透明,但是兼容性不好

    所以选择合适的图片格式很重要,小图片可以用base64

  1. 单页应用路由懒加载

  2. 组件按需加载

    需要在babel里配置一下,比如:

    plugins.push(['import', {
      'libraryName': 'ant-design-vue',
      'libraryDirectory': 'es',
      'style': true // `style: true` 会加载 less 文件
    }])
    
  3. 图片懒加载 可以使用vueLazyLoad

  4. 静态资源上CDN

    CDN:内容分发网络,可以加快用户访问网络资源的速度和稳定性,减轻源服务器的访问压力,就近选择离用户近的CDN服务器进行资源的分配

  5. 升级到HTTP2.0

    HTTP2.0是二进制帧协议,头部体积更小,解决了队头阻塞问题,支持双工,客户端和服务端都可以主动发送数据

  6. preftch 和 preload

  • preload和prefetch的本质都是预加载,即先加载、后执行,加载与执行解耦。

  • preload和prefetch不会阻塞页面的onload。

  • preload用来声明当前页面的关键资源,强制浏览器尽快加载;而prefetch用来声明将来可能用到的资源,在浏览器空闲时进行加载。

  • 不要滥用preload和prefetch,需要在合适的场景中使用。

  1. 开启缓存

    使用强缓存和协商缓存

页面渲染相关优化

  1. 根据页面渲染过程进行合理的资源引入

    页面渲染过程

    a. 解析HTML生成DOM树 - 渲染引擎首先解析HTML文档,生成DOM树

    b. 构建Render树 - 接下来不管是内联式,外联式还是嵌入式引入的CSS样式会被解析生成CSSOM树,根据DOM树与CSSOM树生成另外一棵用于渲染的树-渲染树(Render tree),

    c. 布局Render树 - 然后对渲染树的每个节点进行布局处理,确定其在屏幕上的显示位置

    d. 绘制Render树 - 最后遍历渲染树并用UI后端层将每一个节点绘制出来

    现代浏览器总是并行加载资源,例如,当 HTML 解析器(HTML Parser)被脚本阻塞时,解析器虽然会停止构建DOM,但仍会识别该脚本后面的资源,并进行预加载。

    同时,由于下面两点:

    1. CSS 被视为渲染阻塞资源(包括JS),这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕,才会进行下一阶段。
    2. JavaScript 被认为是解释器阻塞资源,HTML解析会被JS阻塞,它不仅可以读取和修改 DOM 属 性,还可以读取和修改 CSSOM 属性。

    所以存在阻塞的 CSS 资源时,浏览器会延迟 JavaScript 的执行和 DOM 构建

    另外:

    1. 当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行。
    2. JavaScript 可以查询和修改 DOM 与 CSSOM。
    3. CSSOM 构建时,JavaScript 执行将暂停,直至 CSSOM 就绪。

    所以,script 标签的位置很重要。实际使用时,可以遵循下面两个原则:

    1. CSS 优先:引入顺序上,CSS 资源先于 JavaScript 资源。

    2. JavaScript 应尽量少影响 DOM 的构建

  2. 加载CSS推荐用link减少用import

  3. defer和async异步加载脚本

    都是异步加载js资源, 但是区别是async加载完资源后会立即开始执行, 而defer会在整个document解 析完成后执行

  4. 减少回流和重绘

  • 使用visibility:hidden替换display:none

  • 使用transform代替top left

    top是几何属性,操作top会改变节点位置从而引发回流,使用transform:translate3d(x,0,0)代替top,只会引发图层重绘,还会间接启动GPU加速

  • 避免使用Table布局

  • 避免规则层级过多

    浏览器的CSS解析器解析css文件时,对CSS规则是从右到左匹配查找,样式层级过多会影响回流重绘效率,建议保持CSS规则在3层左右

  • 避免节点的属性值放在循环里当做变量

for (let i = 0; i < 10000; i++) {
    const top = document.getElementById("css").style.top;
    console.log(top);
}

const top = document.getElementById("css").style.top;
for (let i = 0; i < 10000; i++) {
    console.log(top);
}
  1. 服务端渲染

    CSR和SSR最大的区别在于前者的页面渲染是JS负责进行的,而后者是服务器端直接返回HTML让浏览器直接渲染,所以客户端渲染要经历js拉代码和执行的过程,就会出现首屏比较慢

动画相关

  • 尽量使用 transition 和 animation来实现CSS动画,而不是JS实现动画(运行在主线程对动画的流畅度有影响)
  • 动画尽量多用transfrom 和 opacity (无需重绘和回流,性能最好
  • translateZ/translate3d 开启硬件加速
  • JS动画使用requestAnimationFrame少用setInterval

代码相关

  1. 高频操作使用防抖和节流

性能指标