前端性能优化

本文最后更新于:2022年4月4日 晚上

【打包时间】

  • 缩小loader的作用范围include,exclude
  • 缩小模块搜索范围
    module.exports = {
      //...
      resolve: {
        modules: [path.resolve(__dirname, 'src'), 'node_modules'], // 搜索位置
        extensions:[".js",".jsx"] // 搜索的后缀名,虽然写的时候可以省略,但会增加搜索的性能损耗
      }
    };

【打包大小】

  • 资源压缩
    css/js/图片压缩

  • 代码分割
    分离第三方库去设置长缓存或者CDN

    module.exports = {
      chainWebpack: config => {
          config.optimizationsplitChunks({
            chunks: 'all',
            cacheGroups: {
              vendors: {
                name: 'chunk-vendors',
                test: /[\\/]node_module[\\/]/,
                priority: 10,
                chunks: 'initial'
              },
              echarts: {
                name: 'chunk-echarts',
                priority: 20,
                test: /[\\/]node_module[\\/]_?echarts(.*)/
              }
            }
          })
        }
    }
  • babel转译优化
    babel会在在每个需要编译的地方都加上helper函数,造成冗余

    // .babelrc
    
    "plugins": [
            "@babel/plugin-transform-runtime"
    ]
    // 自动移除语法转换后内联的辅助函数(inline Babel helpers),使用@babel/runtime/helpers里的辅助函数来替代;
  • 按需打包组件(如element-ui)和tree shaking

  • gzip压缩

    【加载阶段】

    白屏loading提示或者骨架屏

    路由懒加载/异步组件

    // router
    export default new Router({
      routes: [
        {
          path: '/home',
          component: () => import('@/components/home'),
        },
        {
          path: '/about',
          component: () => import('@/components/home'),
        },
      ],
    })
    
    // async component
    import {defineAsyncComponent} from 'vue'

图片懒加载

// 主要逻辑
const imgList = [...document.querySelectAll('img')]
function lazyLoad(imgList){
  let count = 0
  const len = imgList.length
  return function(){
    let doneList = []
    imgList.forEach((img,index)=>{
      const react = img.getBoundingClientRect()
      // Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置
      if(react.top<window.innerHeight){
        img.src = img.dataset.src  // 在这里赋值图片src
        doneList.push(index)
        count++
        if(count === len){
          document.removeEventListener('scroll',lazyLoad)
        }
      }
    })
    imgList = imgList.filter((img,index)=>!doneList.includes(i))
  }
}
document.addEventListener('scroll',_.debounce(lazyLoad))
//

虚拟列表

动态渲染长列表

图片占位符

sqip

优化文档结构

  • css资源请求放在文档最前

  • 脚本放在最后且非核心代码异步加载

    js脚本组织DOM解析,而css资源又回阻塞js执行

    异步加载的方式

    1. 动态创建script标签
    2. scriptdefer
    3. scriptasync
      script
    • 普通的script,html解析暂停,立即下载和执行这个脚本
    • <script async>,html解析和该脚本的加载同时进行,下载完后执行脚本时暂停html解析
    • <script defer>,html解析和该脚本的加载同时进行,脚本在页面解析完成后才执行—>DOMContentLoaded 之前执行

使用CDN

  1. 安装html-webpack-plugin

  2. 修改public/index.html

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta
          name="viewport"
          content="width=device-width,initial-scale=1.0,user-scalable=no"
        />
        <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    
        <!-- 使用CDN的CSS文件 -->
        <% for (var i in htmlWebpackPlugin.options.cdn &&
        htmlWebpackPlugin.options.cdn.css) { %>
        <link
          href="<%= htmlWebpackPlugin.options.cdn.css[i] %>"
          rel="preload"
          as="style"
        />
        <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />
        <% } %>
        <!-- 使用CDN的CSS文件 -->
    
      </head>
      <body>
        <noscript>
          <!--  -->
        </noscript>
        <div id="app"></div>
    
        <!-- built files will be auto injected -->
    
        <!-- 使用CDN的JS文件 -->
        <% for (var i in htmlWebpackPlugin.options.cdn &&
        htmlWebpackPlugin.options.cdn.js) { %>
        <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
        <% } %>
        <!-- 使用CDN的JS文件 -->
      </body>
    </html>
    
  3. 配置vue.config.js

    // 判断生产环境
    const isProduction = process.env.NODE_ENV === "production";
    const cdn = {
      // 随便抓几个
      js: [
        "https://cdn.bootcdn.net/ajax/libs/vue/2.6.0/vue.runtime.esm.js",
        "https://cdn.bootcdn.net/ajax/libs/vue-router/3.1.3/vue-router.esm.js"
      ],
    };
    
    module.exports = {
      configureWebpack: (config) => {
        if (isProduction) {
          config.externals = {
            // 排除的依赖,不从bundle中引入的依赖
            "vue": "Vue",
            "vue-router": "VueRouter"
          };
        }
      },
      chainWebpack: (config) => {
        if (isProduction) {
          config.plugin("html").tap((args) => {
            // 传递给 html-webpack-plugin's 构造函数的新参数
            args[0].cdn = cdn;
            return args;
          });
        }
      },
    };

预解析DNS

DNS Prefetch 是一种DNS 预解析技术,当浏览网页时,浏览器会在加载网页时对网页中的域名进行解析缓存,这样在单击当前网页中的连接时就无需进行DNS的解析,减少用户等待时间,提高用户体验。

  1. HTTP中浏览器会对<a>标签自动开启dns预解析

    对于https页面,大部分浏览器是关闭a标签的dns预解析的

  2. HTTPS用meta信息来告知浏览器, 当前页面要做DNS预解析:
    <meta http-equiv="x-dns-prefetch-control" content="on" />

  3. 在页面<head>中使用<link>标签来强制对DNS预解析:

<link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />

预渲染和服务端渲染

  • SSR:server side render
  • prerender-spa-plugin不适用于个性化的,内容变化大,多路由的页面,可以用来优化单页应用。

【运行阶段】

JS

  • 合理使用浏览器缓存策略
  • 不频繁操作DOM,使用虚拟DOM或fragment批量操作
  • 读取元素的位置大小属性(如scrollTop,offsetHeight…)浏览器会立即重排,更不能将这些属性放入循环中
  • 使用事件委托
  • 使用Web Worker
  • 使用requestAnimationFrame代替有高性能需求的计时器

    CSS

  • 避免css选择器嵌套过深
  • 避免大量使用id选择器,每个id都会变为一个单独的全局变量
  • 避免使用tabletable会频繁触发重排
  • 更换class代替直接修改style
  • 合理使用GPU加速
    • css3动画transform代替修改几何信息left,top...
    • opacity代替visibility
    • 复杂动画元素脱离文档流position:absolute/fixed
    • 加上will-change属性的元素会交给合成层渲染(GPU

【资源优化】

图片资源

高效图片格式

  • WebP
  • 矢量图SVG
  • Base64位图 base64

    响应式图片

    <picture>
    	<source srcset="banner_w1000.jpg" media="(min-width: 801px)">
    	<source srcset="banner_w800.jpg" media="(max-width: 800px)">
    	<img src="banner_w800.jpg" alt="">
    </picture>

    图片压缩

  • tinypng
  • webpack插件

小图合并

  • 合并雪碧图
  • 使用icon-font字体图标
    svg-sprite-loader

性能检测报告

  • Performance
  • Lighthouse

关键时间点

  • FCP页面上呈现第一DOM元素的时间,之前都是白屏
  • TTI页面可以交互的时间
  • LCP视口内最大面积内容渲染时间
const timing = window.performance && window.performance.timing
const navigation = window.performance && window.performance.navigation

// DNS
const DNS = timing.domainLookupEnd - timing.domainLookupStart

// Network
const NetWork = timing.responseEnd - timing.navigationStart

// 渲染
const Processing = (timing.domComplete || timing.domLoading) - timing.domLoading

// 可交互
const Active = timing.domInteractive = timing.navigationStart

前端性能优化
http://yoursite.com/2022/02/24/前端性能优化/
作者
tatekii
发布于
2022年2月24日
许可协议