Vue中深克隆的3种实现方案(附详细注释+测试)

26 阅读6分钟

深克隆(深拷贝)的核心是复制一个对象/数组,且拷贝后的对象与原对象完全独立,修改拷贝对象不会影响原对象(包括嵌套层级的属性/元素)。在Vue开发中,深克隆常用于处理响应式数据(如ref、reactive创建的数据),避免因浅拷贝导致的响应式异常,以下是3种实用实现方案,从简单到完善,覆盖不同场景。

一、核心前提:浅克隆 vs 深克隆(必懂区别)

先明确浅克隆与深克隆的差异,避免混淆:

  • 浅克隆:仅复制对象的第一层属性,嵌套的对象/数组仍指向原引用(修改嵌套内容会影响原对象),如Object.assign()、扩展运算符(...)。
  • 深克隆:复制对象的所有层级(包括嵌套的对象、数组、函数等),拷贝后与原对象完全脱离引用,互不影响(这是Vue开发中常用的方式)。

Vue中注意点:对于reactive创建的响应式对象,直接克隆会丢失响应式特性,需特殊处理;ref对象需先获取.value再克隆。

二、方案1:基础版深克隆(适配普通对象/数组,无复杂类型)

适用于:普通对象、数组,无函数、Date、RegExp等复杂类型,代码简洁,上手快。

核心逻辑:利用JSON.stringify()将对象转为字符串,再用JSON.parse()转回对象,实现深度拷贝(本质是序列化+反序列化)。

// 基础版深克隆(Vue中可直接使用,适配简单数据)
function deepCloneBasic(obj) {
  // 处理null和非对象/数组类型(直接返回,无需克隆)
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  // 利用JSON序列化+反序列化实现深克隆
  return JSON.parse(JSON.stringify(obj));
}

// Vue中测试示例(普通数据)
const obj = { name: 'Vue', info: { version: '3.4.21' }, list: [1, 2, 3] };
const cloneObj = deepCloneBasic(obj);

// 修改拷贝对象,原对象不受影响
cloneObj.info.version = '3.5.0';
cloneObj.list.push(4);
console.log(obj.info.version); // 3.4.21(原对象不变)
console.log(obj.list); // [1,2,3](原对象不变)

优点:代码简洁,无需复杂逻辑,适配大部分简单场景;

缺点:无法克隆函数、Date、RegExp、undefined、Symbol等复杂类型(会丢失或变形),不适合包含这些类型的数据。

三、方案2:完善版深克隆(适配所有类型,通用推荐)

适用于:所有数据类型(对象、数组、函数、Date、RegExp等),解决基础版的缺陷,是Vue开发中最常用的通用方案。

核心逻辑:递归遍历对象/数组,对不同类型的数据分别处理,确保所有层级都被深度拷贝,不丢失类型和属性。

// 完善版深克隆(适配所有类型,Vue通用)
function deepClonePerfect(obj) {
  // 1. 处理null和非对象类型(直接返回)
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 2. 处理Date类型(避免Date被转为普通对象)
  if (obj instanceof Date) {
    return new Date(obj);
  }

  // 3. 处理RegExp类型(保留正则的修饰符)
  if (obj instanceof RegExp) {
    return new RegExp(obj.source, obj.flags);
  }

  // 4. 处理数组和对象(区分数组和普通对象)
  // 创建一个与原对象类型一致的空容器(数组→数组,对象→对象)
  const cloneObj = Array.isArray(obj) ? [] : {};

  // 5. 递归遍历所有属性,深度拷贝
  for (let key in obj) {
    // 只拷贝对象自身的属性(排除原型链上的属性)
    if (obj.hasOwnProperty(key)) {
      // 递归克隆子属性(嵌套对象/数组)
      cloneObj[key] = deepClonePerfect(obj[key]);
    }
  }

  return cloneObj;
}

// Vue中测试示例(复杂数据)
const complexObj = {
  name: '深克隆',
  date: new Date(),
  reg: /vue/g,
  func: () => console.log('vue'),
  info: { a: 1, b: { c: 2 } },
  list: [1, [2, 3]]
};

const cloneComplex = deepClonePerfect(complexObj);
cloneComplex.info.b.c = 100;
cloneComplex.list[1].push(4);
cloneComplex.date.setFullYear(2025);

console.log(complexObj.info.b.c); // 2(原对象不变)
console.log(complexObj.list[1]); // [2,3](原对象不变)
console.log(complexObj.date.getFullYear()); // 2024(原对象不变)
console.log(cloneComplex.func()); // vue(函数正常克隆)

优点:适配所有数据类型,克隆彻底,无数据丢失,满足Vue中复杂数据的克隆需求;

缺点:代码稍复杂,无需额外依赖,可直接封装到Vue项目中复用。

四、方案3:Vue专属深克隆(适配响应式数据,推荐)

适用于:Vue2/Vue3的响应式数据(ref、reactive创建的数据),解决“克隆后丢失响应式”的问题,结合Vue内置API,更贴合Vue开发场景。

核心逻辑:利用Vue内置的reactive、ref API,克隆后重新创建响应式对象,确保克隆后的数据仍保持响应式特性。

1. Vue3 专属实现(适配ref、reactive)

<script setup>
import { ref, reactive, isRef, isReactive, toRaw } from 'vue'

// Vue3专属深克隆(保留响应式)
function deepCloneVue3(obj) {
  // 处理ref对象:先获取原始值,克隆后重新创建ref
  if (isRef(obj)) {
    const rawValue = toRaw(obj.value); // 转为原始值(避免响应式干扰)
    const cloneValue = deepClonePerfect(rawValue); // 用完善版克隆原始值
    return ref(cloneValue); // 重新创建ref,保留响应式
  }

  // 处理reactive对象:先获取原始值,克隆后重新创建reactive
  if (isReactive(obj)) {
    const rawObj = toRaw(obj); // 转为原始对象
    const cloneObj = deepClonePerfect(rawObj); // 深度克隆
    return reactive(cloneObj); // 重新创建reactive,保留响应式
  }

  // 非响应式数据,直接用完善版克隆
  return deepClonePerfect(obj);
}

// 测试示例
const reactiveObj = reactive({ name: 'Vue3', info: { age: 5 } });
const refObj = ref({ a: 1, b: [2, 3] });

// 克隆响应式数据
const cloneReactive = deepCloneVue3(reactiveObj);
const cloneRef = deepCloneVue3(refObj);

// 修改克隆对象,原对象不受影响,且克隆对象仍有响应式
cloneReactive.info.age = 10;
cloneRef.value.b.push(4);

console.log(reactiveObj.info.age); // 5(原对象不变)
console.log(refObj.value.b); // [2,3](原对象不变)
console.log(cloneReactive.info.age); // 10(克隆对象响应式正常)
</script>

2. Vue2 专属实现(适配data、ref、reactive)

// Vue2专属深克隆(保留响应式,需引入Vue)
import Vue from 'vue'

function deepCloneVue2(obj) {
  // 处理Vue2的响应式对象(data中定义的对象、Vue.observable创建的对象)
  if (Vue.isReactive(obj) || Vue.isVue(obj)) {
    // 用Vue.extend创建临时组件,通过props传递数据,实现响应式克隆
    const TempComponent = Vue.extend({
      props: Object.keys(obj),
      render: function() {}
    });
    const instance = new TempComponent({ propsData: obj });
    return instance._props;
  }

  // 处理ref对象(Vue2的ref需通过.value访问)
  if (obj && obj._isRef) {
    const cloneValue = deepClonePerfect(obj.value);
    return Vue.ref(cloneValue);
  }

  // 非响应式数据,用完善版克隆
  return deepClonePerfect(obj);
}

// 测试示例(Vue2)
const vue2Reactive = Vue.observable({ name: 'Vue2', list: [1, 2] });
const cloneVue2 = deepCloneVue2(vue2Reactive);
cloneVue2.list.push(3);
console.log(vue2Reactive.list); // [1,2](原对象不变)
console.log(cloneVue2.list); // [1,2,3](克隆对象响应式正常)

优点:专门适配Vue响应式数据,克隆后不丢失响应式特性,贴合Vue开发场景;

缺点:依赖Vue内置API,需区分Vue2和Vue3版本。

五、Vue中深克隆的注意事项

  • 克隆响应式数据时,不可直接用基础版(JSON方式),会丢失响应式特性,需用Vue专属方案;
  • Vue3中,reactive对象克隆前需用toRaw()转为原始对象,否则会因响应式代理导致克隆异常;
  • 避免克隆Vue组件实例、Vuex状态等特殊对象,这类对象克隆后可能无法正常使用;
  • 若项目中频繁使用深克隆,可将完善版/Vue专属版封装为全局工具函数(如utils/clone.js),方便复用;
  • 简单场景(无复杂类型、非响应式数据)可直接用基础版,复杂场景优先用完善版,响应式数据用Vue专属版。

六、总结(方案选择建议)

  1. 简单场景(普通对象/数组,无复杂类型):方案1(基础版),简洁高效;
  2. 通用场景(所有数据类型,非响应式):方案2(完善版),克隆彻底,无数据丢失;
  3. Vue开发场景(响应式数据):方案3(Vue专属版),保留响应式,贴合Vue特性。

实际开发中,可根据数据类型和是否为响应式,选择对应的方案,建议将方案2和方案3封装为全局工具,提升开发效率。