Bootstrap

Vue 性能革命:揭秘前端优化的终极技巧;Vue优化技巧,解决Vue项目卡顿问题

目录

Vue优化路径

一、使用key

二、使用冻结对象

三、使用函数式组件

四、使用计算属性

五、使用非实时绑定的表单项

六、保持对象引用稳定

6.1、保持对象引用稳定定义

6.2、保持对象引用稳定与不稳定的例子

6.3、vue2判断数据是否变化是通过hasChanged函数实现的

①为什么在x === y成立的时候,还要做一个return x === 0 && 1 / x !== 1 / (y as number)的判断?

②为什么在 x === y 不成立的时候,还要做一个x === x || y === y的判断?

七、使用v-show替代v-if

八、使用延迟装载(defer)

九、使用keep-alive

十、长列表优化

十一、打包体积优化

十二、总结与相关资源


Vue优化路径

一、使用key

        也就是使用v-for这样通过循环生成的列表,应给每个列表项一个稳定且唯一的key,这样有利于在列表变动时,尽量少的删除、新增、改动元素。

二、使用冻结对象

        在不需要改动的数据时(比如只读的数据:面向顾客的商品列表等),将对象冻结。例如:

var obj = { a : 1, b : 2}

Object.freeze(obj)

obj.a = 3 // 无效,obg.a依然为1

// 此时obj的属性无法访问,自然也无法通过Object.defineProperty来实现响应式

Object.isFrozen(obj) // 返回true,检查是否冻结

        冻结的对象不会被响应化,节约了observe该对象每个属性的资源(添加getter和setter)

        举一个例子,可以做一个demo实际体验一下效率的区别,做个页面放两个按钮,分别绑定loadFrozenDatas和loadNormalDatas方法,就能明显感受到生成同样的数据,冻结的对象生成速度显著快于不冻结的对象生成。

data() {
    return{
        normalDatas: [],
        freezeDatas: [],
    };
},
methods: {
    loadNormalDatas(){
        this.normalDatas = this.getDatas(); console.log("normalDatas", this.normalDatas);
    },
    loadFrozenDatas() {
        this.freezeDatas = Object.freeze(this.getDatas()); console.log("freezeDatas", this.freezeDatas);
    },
    getDatas(){
        const result = [];
        for (var i = 0; i < 1000000; i++) {
            result.push({
                id: i,
                name:`name${i}`,
                address:{
                    city:`city${i}`,
                    province:`province${i}`,
                },
            });
        }
        return result;
    },
}

observe中会调用Object.defineProperty(),通过属性描述符为对象的每个属性实现响应式

属性描述符详情请看:属性描述符初探——Vue实现数据劫持的基础

三、使用函数式组件

        在Vue.js中,函数式组件是一种没有状态和实例的概念的组件。函数式组件主要用于声明性地描述UI,它们接受 props 作为输入,并返回一个Vue元素树作为输出。

        函数式组件不会通过new VueComponent生成新的vue实例,不会加入到vue的组件树中,只做页面渲染,节省性能。

// Vue 2中的函数式组件:

Vue.component('my-functional-component', {
  functional: true,
  render: function (createElement, context) {
    // 使用createElement创建元素
    return createElement('div', context.props.text);
  }
});


// Vue 3中的函数式组件:

import { h, FunctionalComponent } from 'vue';

const MyFunctionalComponent: FunctionalComponent = (props, { slots }) => {
  return h('div', props.text, slots().default);
};

export default MyFunctionalComponent;

四、使用计算属性

        计算属性可以缓存(只有所依赖的数据变化了才会重新计算),如果模版中数据会使用多次,就可以使用计算属性。

五、使用非实时绑定的表单项

        双向绑定会导致任意一端修改数据均会导致重渲染rerender,在不需要双向绑定的位置(比如只开放只读数据)或者不需要保持实时数据双向绑定的情况下(比如输入框内容和页面某元素绑定,输入过程中每按一次键盘都会导致一次重新渲染)不使用v-model。

        也可以通过v-model.lazy来允许某一时间内数据与表单内容不一致,也就是从监听@input变成了监听@change。

六、保持对象引用稳定

6.1、保持对象引用稳定定义

        大多数情况下,vue触发重渲染的时机是依赖数据发生变化的时机,若数据没有变化,哪怕重新给数据赋值,vue也不会做出反应。

        因此,哪怕读取属性所属的对象值没变,但是引用变了,也会导致页面重新渲染。 

6.2、保持对象引用稳定与不稳定的例子

        现在页面上渲染了一个表格,由一系列对象数据生成。如果要在数据库增加一行。那么读取数据库增加的数据,并将其添加到现有的表格数据中,效率会比直接从数据库读取全部数据,然后赋值给表格绑定的数据上要高。

        因为读取增加的数据,然后修改表格绑定的数据,只有变化的数据会重新渲染,原先有的表格行不会重渲染,可如果直接把增加后的全部数据(一个引用不同的新对象)赋值给表格绑定的数据,就会导致所有行全部重新渲染,哪怕大多数行数据并没有变化

6.3、vue2判断数据是否变化是通过hasChanged函数实现的

function hasChanged(x : unknown, y : unknown) :boolean{
  if (x === y){
    return x === 0 && 1 / x !== 1 / (y as number)
  } else {
    return x === x || y === y
  }
}

        这里x与y分别是新值和旧值。

        正常情况下,如果x===y,代表没有改变,返回false,反之则返回true。

①为什么在x === y成立的时候,还要做一个return x === 0 && 1 / x !== 1 / (y as number)的判断?

        +0 === -0的判断恒为true,但二者实际不相等。所以先判断x是否为0+或0-,如果不是,则直接触发短路返回false,如果是,就通过求倒数,比较倒数是否相等,如果均为0+或均为0-,则依然返回false,若倒数为Infinity和-Infinity,这样就会返回true,从而排除从0+变为0-,但是却新旧值却相等的情况。

②为什么在 x === y 不成立的时候,还要做一个x === x || y === y的判断?

        因为如果NaN === NaN的判断恒为false,所以哪怕不相等,还要做一下自判断,若新旧值都是NaN,则返回false。排除x与y都为NaN,但是新旧值却不相等的情况。

七、使用v-show替代v-if

        对于频繁切换显示状态的元素,使用v-show可以保证虚拟dom树的稳定,尤其是对于那些内部包含大量dom元素的节点,这一点极其重要。

        DOM树只与布局有关,与显示与否无关。使用v-show渲染的元素,不管返回值是什么,都会添加到dom树中,但是使用v-if渲染的元素,只有为true的时候才会添加到DOM树中。

八、使用延迟装载(defer)

        HTML中的<script>标签有一个可选的defer属性。当脚本设置了defer属性后,它会被告知浏览器在文档解析完成后再执行这个脚本,而不是立即执行。

        对于图片和其他资源,可以使用 v-lazy-image 或 v-lazy-component 这样的 Vue 指令来实现懒加载。

<!-- 在模板中使用 -->
<img v-lazy="imageSrc" alt="Lazy Image">

        使用像 vue-lazyload 这样的库来实现图片和组件的懒加载。 

// 安装 vue-lazyload
npm install vue-lazyload --save

// 在 main.js 中使用
import Vue from 'vue';
import VueLazyload from 'vue-lazyload';

Vue.use(VueLazyload, {
  preLoad: 1.64,
  error: 'error.png',
  loading: 'loading.png',
  attempt: 1
});

九、使用keep-alive

        keep-alive 是 Vue 的一个内置组件,用于缓存不活动的组件实例,避免重复创建和销毁组件,从而提高性能。这在单页面应用(SPA)中特别有用,可以保持用户状态和避免不必要的重新渲染。

<!-- 在路由出口使用 -->
<router-view v-if="$route.meta.keepAlive"></router-view>
<keep-alive>
  <router-view v-if="!$route.meta.keepAlive"></router-view>
</keep-alive>

        这里会根据路由的 meta 属性决定是否使用 keep-alive。

十、长列表优化

        海量数据渲染容易导致卡顿,除了可以运用第六点保持引用对象稳定外,Vue还提供了几种方法来优化长列表的渲染:

  • 虚拟滚动(Virtual Scrolling):只渲染可视区域内的元素,而不是渲染整个列表。可以使用 vue-virtual-scroll-list 或 vue-virtual-scroller 等库来实现。
  • 分页或无限滚动:将数据分批次加载,而不是一次性加载所有数据。
  • 节流(Throttle)和防抖(Debounce):在处理滚动事件等频繁触发的操作时,使用节流或防抖技术减少事件处理的频率。

        此外,element plus也提供了虚拟化组件,用来处理海量数据的渲染问题

十一、打包体积优化

        减少最终打包文件的大小可以提高应用的加载速度和性能。常见方法包括:

  1. 代码分割(Code Splitting):使用 Webpack 的动态 import() 语法来实现代码分割,只加载用户实际需要的代码。
  2. Tree Shaking:移除未使用的代码,Webpack 等现代打包工具可以自动进行 Tree Shaking
  3. 使用 Vue CLI 的优化选项:Vue CLI 提供了多种优化配置,如 vue-cli-service build --report 可以生成报告,帮助分析打包体积。
  4. 移除 console.log:在生产环境中,确保移除所有的 console.log 语句,因为它们会增加打包体积。
  5. 使用 Terser 或 UglifyJS 压缩 JavaScript:这些工具可以压缩 JavaScript 代码,减少文件大小。

十二、总结与相关资源

        Vue在处理少量数据和有限dom的情况下技术已经非常成熟了,但现在随着AI时代的到来,海量数据场景会越来越多,Vue优化技巧也是必备技能。

        博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

        更多优质内容,请关注:

JS底层逻辑:

        最细最有条理解析:事件循环(消息循环)是什么?进程与线程的定义、关系与差异

        路由通配符,小小的字符有大大的作用,你真的熟悉吗? 

        管理数据必备!侦听器watch用法详解

        什么是深拷贝?深拷贝和浅拷贝有什么区别

JS语法篇:

        属性描述符初探——Vue实现数据劫持的基础

        你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解

        对象数据的读取,看这一篇就够了!

        通过array.every()实现数据验证、权限检查和一致性检查,array.some与array.every的区别

        通过array.some()实现权限检查、表单验证、库存管理、内容审查和数据处理

        通过array.map()实现数据转换、创建派生数组、异步数据流处理、搜索和过滤等需求

        通过array.reduce()实现数据汇总、条件筛选和映射、对象属性的扁平化、转换数据格式等

        通过array.filter()实现数组的数据筛选、数据清洗和链式调用

巧妙算法与窍门:

        多维数组操作,不要再用遍历循环foreach了,来试试数组展平的小妙招!

        别再用双层遍历循环来做新旧数组对比,寻找新增元素了!

        shpfile转GeoJSON且控制转化精度;如何获取GeoJSON?GeoJson结构详解

        Mapbox添加行政区矢量图层、分级设色图层、自定义鼠标悬浮框、添加天地图底图等

Element plus拓展:

        通过el-tree自定义渲染网页版工作目录,实现鼠标悬浮显示完整名称等

        el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能

        el-table中如何添加渐变色带、多色色带

;