性能优化 - Vue 日常实践优化

在 Vue 应用开发过程中,通过合理使用框架特性、避免性能陷阱、优化组件设计等方式,从代码层面提升应用性能。

优化实践

使用内置 API 完成需求

JIT 是浏览器中的 JavaScript 引擎在运行时,把热点 JavaScript 代码即时编译为机器码,从而提高执行性能的一种机制。

Chrome 浏览器 JIT 指内置的 JavaScript 引擎 V8 的即时编译机制。

使用 DOM API / BOM API / Vue API 完成开发,DOM API / BOM API / Vue API 通常有对应的优化算法,必要时或节约开发成本时再使用第三方工具类库辅助。

  • API 底层都是基于 V8 引擎,相比 JS 层 JIT 编译执行而言,基于 C++ 的 V8 会更快。
  • sort 会根据情况采用归并或者插入排序(小数组采用插入,大数组采用归并或者是归并 + 插入的混合排序方案),相比手动实现的 JS 排序功能更优。
  • 遍历时使用 for...of / for 代替 for...infor...in 语义需要收集对象自身的枚举属性,然后再去遍历原型链上的属性,过滤掉不可枚举的属性值,然后处理属性遮蔽问题,最终动态决定遍历结果,这个无法预测的枚举流程是无法被跳过的,在数组遍历时只能执行慢路径查找。
javascript 复制代码
// 推荐:使用 for...of 或 for 循环
const arr = [1, 2, 3, 4, 5];
for (const item of arr) {
  console.log(item);
}

// 或使用传统 for 循环
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

// 避免:使用 for...in 遍历数组(性能较差)
for (const index in arr) {
  console.log(arr[index]);
}

避免非必要响应式

避免将非响应式实例声明为响应式,尤其注意不要将插件实例声明为响应式,在 Vue2 中会由于深度递归响应,导致大量内存被占用,严重会出现假死的情况。

在 Vue3 中,可以使用 shallowRefshallowReactive 来避免非必要的响应逻辑。

javascript 复制代码
// ❌ 将插件实例声明为响应式
export default {
  data() {
    return {
      // Vue2 会深度递归处理,导致性能问题
      echart: null,
      moment: moment,
    };
  },
  mounted(){
      this.echart = echart.init();
  }
};

// ✅ 使用 Object.freeze 或直接挂载到 this
export default {
  data() {
    return {
      // 使用 Object.freeze 冻结对象,避免响应式处理
      // Vue 处理响应式之前会去判断 configurable 描述符是否可用,不可用不会进行响应式处理
      axios: Object.freeze(axios),
    };
  },
  created() {
    // 或直接挂载到实例上,不放入 data
    this.axios = axios;
  },
};

Keepalive 缓存

使用 <keep-alive> 缓存组件,根据需求进行缓存,小型项目可以直接进行 RouterView 级别缓存,大型项目指定对大型组件的缓存,同时通过 max 指定最大缓存组件数,注意要在 activateddeactivated 中完成组件的唤醒和卸载处理。

html 复制代码
<!-- 小型项目:RouterView 级别缓存 -->
<template>
  <keep-alive :max="10">
    <router-view />
  </keep-alive>
</template>

<!-- 大型项目:指定组件缓存 -->
<template>
  <keep-alive :include="['ComponentA', 'ComponentB']" :max="5">
    <component :is="currentComponent" />
  </keep-alive>
</template>

<script>
export default {
  name: "ComponentA",
  activated() {
    // 组件被激活时执行
    // 完成定时器、事件、数据请求
    this.setTimers();
    this.refreshData();
  },
  deactivated() {
    // 组件被停用时执行
    // 销毁定时器,事件,避免内存泄漏和逻辑冲突
    this.clearTimers();
  },
};
</script>

v-if 和 v-show

频繁切换避免使用 v-if,使用 v-show,由于 v-show 会导致代码不太美观,可以的话通过 <component :is="bindComponent"> 或者 v-if 结合 <keep-alive> 实现类似的逻辑。

html 复制代码
<template>
  <!-- ❌ v-if 会销毁和重建组件 -->
  <div v-if="showComponent">组件内容</div>

  <!-- ✅ v-show 只切换 display 样式 -->
  <div v-show="showComponent">组件内容</div>

  <!-- ✅ 使用 component 动态组件 + keep-alive 组合,保持代码美观,配合异步组件优化首次渲染体积,基于 keep-alive 实现异步组件缓存策略 -->
  <keep-alive>
    <component :is="currentComponent" />
  </keep-alive>
</template>

<script>
export default {
  components: {
    // Vue3 用 defineAsyncComponent 封装引入
    ComponentA: () => import("./components/componentA"),
    ComponentB: () => import("./components/componentB"),
  },
  data() {
    return {
      showComponent: true,
      currentComponent: "ComponentA",
    };
  },
};
</script>

Computed 缓存

计算内容可以用 computed 计算属性去缓存,computed 会在依赖属性更新触发视图更新时,才会根据 dirty 标志位判断依赖属性是否更新,决定计算新值还是获取缓存值。

html 复制代码
<template>
  <div>
    <p>总价:{{ totalPrice }}</p>
    <p>折扣价:{{ discountPrice }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { price: 10, quantity: 2 },
        { price: 20, quantity: 3 },
      ],
      discount: 0.8,
    };
  },
  computed: {
    // ✅ 依赖变化时才重新计算,否则使用缓存
    totalPrice() {
      return this.items.reduce(
        (sum, item) => sum + item.price * item.quantity,
        0
      );
    },
    // 依赖 totalPrice 计算结果,totalPrice 缓存时 discount 不会重复计算
    discountPrice() {
      return this.totalPrice * this.discount;
    },
  },
  // ❌ 不推荐:使用 methods 每次都会重新计算
  methods: {
    getTotalPrice() {
      return this.items.reduce(
        (sum, item) => sum + item.price * item.quantity,
        0
      );
    },
  },
};
</script>

内部创建和销毁

销毁监听器、定时器、实例,避免出现内存泄漏影响性能问题。

html 复制代码
<script>
export default {
  data() {
    return {
      timer: null,
      resizeHandler: null,
    };
  },
  mounted() {
    // 创建定时器
    this.timer = setInterval(() => {
      console.log("定时任务");
    }, 1000);

    // 小技巧:通过 AbortController 可以同时销毁多个事件监听器
    this.controller = new AbortController();
    window.addEventListener("scroll", this.scrollHandler, {
      signal: this.controller.signal,
    });
    window.addEventListener("resize", this.resizeHandler, {
      signal: this.controller.signal,
    });

    // 三方插件初始化
    this.thirdPlugin = plugin.init();
  },
  methods: {
    resizeHandler() {
      // resize 事件
    },
    scrollHandler() {
      // scroll 事件
    },
  },
  beforeDestroy() {
    // ✅ 销毁定时器
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = null;
    }
    // ✅ 销毁事件监听
    if (this.controller) {
      this.controller.abort();
      this.controller = null;
    }

    // ✅ 销毁插件实例
    if (this.thirdPlugin) {
      // 先看三方插件内部是否有销毁 API 主动执行,避免插件内部引用未销毁导致泄漏
      this.thirdPlugin.destroy();
      this.thirdPlugin = null;
    }
  },
};
</script>

异步组件

Vue-Router 和 Vue 通过异步组件方式按需引入,必要时再进行加载。

但是要避免过度拆分,当 组件拆分前请求成本(请求耗时、请求体积、是否重复) > 组件拆分后的请求成本 时,再考虑进行拆分。

javascript 复制代码
import Home from "@/views/Home.vue";

// Vue Router 异步组件
const routes = [
  {
    path: "/home",
    // 首页不推荐异步逻辑,避免首页加载白屏
    component: Home,
  },
  {
    path: "/large-page",
    // ✅ 使用 webpackChunkName 指定 chunk 名称,避免过渡拆包,可以把同个页面下的子路由都集中在webpackChunk中加载,也可以按需拆分
    component: () =>
      import(/* webpackChunkName: "large-page" */ "@/views/LargePage.vue"),
  },
];

// Vue 组件异步加载
export default {
  components: {
    // ✅ 异步组件:按需加载
    HeavyComponent: () => import("@/components/HeavyComponent.vue"),
    // ✅ 带加载状态和错误处理, Vue3 使用 defineComponent API
    AsyncComponent: () => ({
      component: import("@/components/AsyncComponent.vue"),
      loading: LoadingComponent,
      error: ErrorComponent,
      delay: 200, // 延迟显示 loading
      timeout: 3000, // 超时时间
    }),
  },
};

Vue 官方优化实践

总结

  • 使用内置 API :优先使用 DOM API / BOM API / Vue API 完成开发,这些 API 基于 V8 引擎优化,性能优于手动实现的 JavaScript 代码。遍历时使用 for...of / for 代替 for...in,避免慢路径查找。
  • 避免非必要响应式 :避免将插件实例等非响应式对象声明为响应式,在 Vue2 中会由于深度递归响应导致性能问题。使用 Object.freeze 冻结对象或直接挂载到实例上,避免响应式处理。
  • 组件缓存 :使用 <keep-alive> 缓存组件,小型项目可以直接进行 RouterView 级别缓存,大型项目指定对大型组件的缓存,同时通过 max 指定最大缓存组件数。注意要在 activateddeactivated 中完成组件的唤醒和卸载处理。
  • 条件渲染优化 :频繁切换避免使用 v-if,使用 v-show。可以通过 <component :is="bindComponent"> 或者 v-if 结合 <keep-alive> 实现类似的逻辑,保持代码美观的同时优化性能。
  • 计算属性缓存 :使用 computed 计算属性缓存计算结果,computed 会在依赖属性更新触发视图更新时,根据 dirty 标志位判断是否重新计算,否则使用缓存值。
  • 资源清理 :及时销毁监听器、定时器、实例,避免内存泄漏。可以使用 AbortController 同时销毁多个事件监听器,对于三方插件需要主动调用销毁 API。
  • 异步组件:通过异步组件方式按需引入,优化首屏加载速度。但要避免过度拆分,需要权衡组件拆分前后的请求成本(请求耗时、请求体积、是否重复)。
相关推荐
董世昌419 小时前
添加、删除、替换、插入元素的全方法指南
java·开发语言·前端
Yu_Lijing9 小时前
基于C++的《Head First设计模式》笔记——抽象工厂模式
c++·笔记·设计模式
创作者mateo9 小时前
PyTorch 入门学习笔记(实战篇)二
pytorch·笔记·学习
HuDie3409 小时前
因果推断与AB实验
笔记
小当家.1059 小时前
JVM八股详解(上部):核心原理与内存管理
java·jvm·学习·面试
qq_316837759 小时前
Element-Plus el-table lazy 自动更新子列表
前端·vue.js·elementui
xiaoxue..9 小时前
把大模型装进自己电脑:Ollama 本地部署大模型完全指南
javascript·面试·node.js·大模型·ollama
逑之9 小时前
C语言笔记8:操作符
c语言·开发语言·笔记
知识分享小能手9 小时前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04 中的大数据 —— 知识点详解 (24)
大数据·学习·ubuntu