vue项目性能优化(持续更新...)

433 阅读8分钟

vue项目性能优化

代码优化

1. v-if 和 v-show 区分使用场景

  • v-if是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的
  • v-show元素总是会被渲染,并且只是简单地基于 CSS 的 display 属性进行切换
  • v-if适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景

2. computed 和 watch 区分使用场景

  • computed是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值
  • watch更多的是「观察」的作用,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作
  • 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed
  • 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch

3. v-for注意

  • v-for 遍历必须为 item 添加 key
  • v-for 遍历避免同时使用 v-if,v-for 比 v-if 优先级高

4. 长列表性能优化(Object.freeze)

有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间

export default {
  data: () => ({
    users: {}
  }),
  async created() {
    const users = await axios.get("/api/users");
    this.users = Object.freeze(users);
  }
};

5. 事件和定时器的销毁

  • 在 js 内使用 addEventListene 等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露
created() {
  addEventListener('click', function() {
    console.log('click')
  }, false)
  this.timer = setInterval(function() {
    console.log('timer')
  }, 1000);
},
beforeDestroy() {
  removeEventListener('click', this.click, false)
  clearInterval(this.timer)
}

6. 图片资源懒加载

  • 对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。我们在项目中可以使用 vue-lazyload 插件:

    • 6.1 安装插件:

      npm install vue-lazyload --save-dev
      
    • 6.2 在入口文件 man.js 中引入并使用:

      import VueLazyload from 'vue-lazyload'
      Vue.use(VueLazyload)
      
    • 6.3 vue 文件中将 img 标签的 src 属性直接改为 v-lazy ,从而将图片显示方式更改为懒加载显示:

      <img v-lazy="/static/img/1.png">
      

7. 路由懒加载

  • Vue是单页面应用,可能会有很多的路由引入 ,这样使用 webpcak 打包后的文件很大,当进入首页时,加载的资源过多,页面会出现白屏的情况,不利于用户体验。把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})

8. 第三方插件的按需引入

  • 如果我们直接引入整个第三方插件,会导致项目的体积太大,我们可以借助 babel-plugin-component ,然后可以只引入需要的组件,以达到减小项目体积的目的

    • 8.1 安装 babel-plugin-component:

      npm install babel-plugin-component -D
      
    • 8.2 然后,将 .babelrc 修改为:

      {
        "presets": [["es2015", { "modules": false }]],
        "plugins": [
          [
            "component",
            {
              "libraryName": "element-ui",
              "styleLibraryName": "theme-chalk"
            }
          ]
        ]
      }
      
    • 8.3 在 main.js 中引入部分组件:

      import Vue from 'vue';
      import { Button, Select } from 'element-ui';
      
      Vue.use(Button)
      Vue.use(Select)
      

9. 服务端渲染 SSR

  • 服务端渲染是指 Vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端这个过程就叫做服务端渲染。
    • (1)服务端渲染的优点:
      • 更好的 SEO:SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),搜索引擎爬取工具可以抓取渲染好的页面
      • 首屏加载更快:SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间
    • (2)服务端渲染的缺点:
      • 服务端渲染只支持 beforCreate 和 created 两个钩子函数
      • 服务端渲染应用程序,需要处于 Node.js server 运行环境
      • 更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源

10. 代码模块化、组件化

  • 把很多常用的地方封装成单独的组件,在需要用到的地方引用,而不是写过多重复的代码,每一个组件都要明确含义,复用性越高越好,可配置型越强越好,css也可以通过less和sass的自定义css变量或者公共样式来减少重复代码

webpack优化(vue-cli3项目)

1. productionSourceMap优化

  • 把productionSourceMap 置为false,既可以减少包大小,也可以加密源码,在vue.config.js中module.exports写入:
module.exports = {
    productionSourceMap: false
}

2. 图片压缩

  • 正常打包之后一些图片文件很大,使打包体积很大,通过image-webpack-loader插件可将大的图片进行压缩从而缩小打包体积
    • 先安装依赖:

      cnpm install image-webpack-loader --save-dev
      
    • 在vue.config.js中module.exports写入:

      module.exports = {
        productionSourceMap: false,
        chainWebpack: config => {
          config.module
            .rule('images')
            .use('image-webpack-loader')
            .loader('image-webpack-loader')
            .options({ bypassOnDebug: true })
            .end()
        }
      }
      

3. 使用CDN

  • vue.config.js配置:
const isProduction = process.env.NODE_ENV !== 'development'
const devNeedCdn = false
const cdn = {
  externals: {
      vue: 'Vue',
      vuex: 'Vuex',
      'vue-router': 'VueRouter'
  },
  css: [],
  js: [
      'https://cdn.staticfile.org/vue/2.6.10/vue.min.js',
      'https://cdn.staticfile.org/vuex/3.0.1/vuex.min.js',
      'https://cdn.staticfile.org/vue-router/3.0.3/vue-router.min.js'
  ]
}
module.exports = {
    productionSourceMap: false,
    chainWebpack: config => {
        config.plugin('html').tap(args => {
            if (isProduction || devNeedCdn) args[0].cdn = cdn
            return args
        })
    },
    configureWebpack: config => {
        if (isProduction || devNeedCdn) config.externals = cdn.externals
    }
}
  • 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" />
        <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="stylesheet"
        />
        <% } %>
        <title>cli3_base</title>
    </head>
    <body>
        <noscript>
            <strong
                >We're sorry but cli3_base doesn't work properly without
                JavaScript enabled. Please enable it to continue.</strong
            >
        </noscript>
        <div id="app"></div>
        <!-- 使用CDN的JS文件 -->
        <% for (var i in htmlWebpackPlugin.options.cdn &&
        htmlWebpackPlugin.options.cdn.js) { %>
        <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
        <% } %>
    </body>
</html>

4. 优化scss配置文件的引入

  • 我们在搭建项目的过程中经常性的会将一些scss配置文件抽离出来,例如主题色等,然后在每个需要的组件中引入。这样会显得很繁琐,我们可以借助sass-loader帮我们进行预处理, 这样我们就不用在每一个页面都进行引入样式,就能直接引用

  • vue.config.js 配置:

module.exports = {
  css: {
    loaderOptions: {
        sass: {
            data: `@import "@/stylePath/theme.scss";`
        }
    },
  },
}

5. 配置代码压缩

// 安装
npm install uglifyjs-webpack-plugin

// 在vue.config.js文件中添加配置
configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
        config.plugins.push(
            new UglifyJsPlugin({
                uglifyOptions: {
                    compress: {
                        warnings: false,
                        drop_debugger: true,
                        drop_console: true,
                    },
                },
                sourceMap: false,
                parallel: true,
            })
        );
    }
}

6. 针对请求数进行优化

  • 有些时候我们发现请求数增多是因为我们页面预先渲染了其它组件,会在html页面中插入像这样的东西<link rel="prefetch">,它是一种 resource hint,用来告诉浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容。可以通过 chainWebpack 的 config.plugin('prefetch') 进行修改和删除:
// vue.config.js
module.exports = {
  chainWebpack: config => {
    // 移除 prefetch 插件
    config.plugins.delete('prefetch')
    // 移除 preload 插件
    config.plugins.delete('preload');
  }
}

7. 开启gzip压缩

// 安装插件
npm i -D compression-webpack-plugin

// vue.config.js 配置
const CompressionPlugin = require("compression-webpack-plugin")
module.exports = {
  configureWebpack:config=>{
    if(progress.env.NODE_ENV === 'production'){
      const productionGzipExtensions = ['html', 'js', 'css']
      config.plugins.push(
        new CompressionWebpackPlugin({
          filename: '[path].gz[query]',
          algorithm: 'gzip',
          test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
          threshold: 10240, // 只有大小大于该值的资源会被处理 10240
          minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
          deleteOriginalAssets: false // 删除原文件
        })
      )
    }
  }
}

8. 公共代码提取

module.exports = {
  configureWebpack:config=>{
    if(progress.env.NODE_ENV === 'production'){
      config.optimization = {
        splitChunks: {
          cacheGroups: {
            vendor: {
              chunks: 'all',
              test: /node_modules/,
              name: 'vendor',
              minChunks: 1,
              maxInitialRequests: 5,
              minSize: 0,
              priority: 100
            },
            common: {
              chunks: 'all',
              test: /[\\/]src[\\/]js[\\/]/,
              name: 'common',
              minChunks: 2,
              maxInitialRequests: 5,
              minSize: 0,
              priority: 60
            },
            styles: {
              name: 'styles',
              test: /\.(sa|sc|c)ss$/,
              chunks: 'all',
              enforce: true
            },
            runtimeChunk: {
              name: 'manifest'
            }
          }
        }
      }
    }
  }
}

基础的前端技术优化

1. 浏览器缓存

  • 为了提高用户加载页面的速度,对静态资源进行缓存是非常必要的,根据是否需要重新向服务器发起请求来分类,将 HTTP 缓存规则分为两大类(强制缓存,对比缓存)
    • 强制缓存:服务器通知浏览器一个缓存时间,在缓存时间内,下次请求直接用缓存。如果生效,不再和服务器发生交互。
    • 对比缓存:将缓存信息中的Etag和Last-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存,返回200就是一次新的请求。不管是否生效,都要和服务器端发生交互
    • 强制缓存优先级高于对比缓存
    • 在对比缓存中Etag要优于Last-Modified

2. 减少重绘和重排

  • 用 JavaScript 修改样式时,最好不要直接写样式,而是替换 class 来改变样式。

    var el = document.querySelector('.el');
    el.style.borderLeft = '1px';
    el.style.borderRight = '2px';
    el.style.padding = '5px';
    
    // css 
    .active {
      padding: 5px;
      border-left: 1px;
      border-right: 2px;
    }
    // javascript
    var el = document.querySelector('.el');
    el.className = 'active';
    
  • 如果要对 DOM 元素执行一系列操作,可以将 DOM 元素脱离文档流,修改完成后,再将它带回文档。推荐使用隐藏元素(display:none)或文档碎片(DocumentFragement),都能很好的实现这个方案。

    • 隐藏元素,进行修改后,然后再显示该元素

      let ul = document.querySelector('#mylist');
      ul.style.display = 'none';
      appendNode(ul, data); // 操作dom
      ul.style.display = 'block';
      
    • 将原始元素拷贝到一个独立的节点中,操作这个节点,然后覆盖原始元素

      let old = document.querySelector('#mylist');
      let clone = old.cloneNode(true);
      appendNode(clone, data); // 操作dom
      old.parentNode.replaceChild(clone, old);
      ```
      
      
  • 缓存布局信息,当访问诸如offsetLeft,clientTop这种属性时,浏览器会强制刷新渲染队列以获取最新的offsetLef,clientTopt值,所以我们应该尽量减少对布局信息的查询次数,查询时,将其赋值给局部变量,使用局部变量参与计算

  • 例如,将元素div向右下方平移,每次移动1px,起始位置100px, 100px:

    • 性能糟糕的代码:

        div.style.left = 1 + div.offsetLeft + 'px';
        div.style.top = 1 + div.offsetTop + 'px';
      
    • 将这个值保存下来,避免重复取值:

        var current = div.offsetLeft;
        div.style.left = 1 + ++current + 'px';
        div.style.top = 1 + ++current + 'px';
      

3. HTML5缓存

  • 合理利用h5的新特性(localStorage、sessionStorage)做一些静态数据的存储,避免重复向后台请求数据

4. 节流和防抖

  • 防抖:就是指触发事件后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则会重新计算函数执行时间。
function debounce(fn,wait){
  let timmer = null;
  return function(){
    var args = arguments;
    var now = !timmer;
    timmer && clearTimeout(timmer);
    timmer = setTimeout(function(){
      timmer = null;
    },wait);
    if(now){
      fn.apply(this,args)
    }
  }
}
  • 节流:就是指连续触发事件,但是在一段时间中只执行一次函数。
 function throttle(func, delay){
  let timer = null
  return function(...arg){
    if(!timer){
      timer = setTimeout(()=>{
        func.apply(this, arg)
        timer = null
      }, delay)
    }
  }
}