Vue3/JavaScript 对象复制:浅拷贝与深拷贝

代码示例:

import { cloneDeep } from "lodash-es";

// 新增
const handleAdd = async () => {
  // 重置
  await handleInfoReset();

  // capitalInfoStore.setCurrentSelectedCapitalInfo(emptyCapitalInfo); // ❌浅拷贝:直接引用源对象,修改源数据可能影响目标,修改localCapitalInfo会影响sourceData,从而影响capitalInfoStore.currentSelectedCapitalInfo,从而影响emptyCapitalInfo
  // capitalInfoStore.setCurrentSelectedCapitalInfo({ ...emptyCapitalInfo }); // ✅浅拷贝:创建新对象,与源对象解耦,与源数据隔离,更安全,修改localCapitalInfo不会影响sourceData、capitalInfoStore.currentSelectedCapitalInfo和emptyCapitalInfo
  // capitalInfoStore.setCurrentSelectedCapitalInfo(structuredClone(emptyCapitalInfo)); // ✅深拷贝:现代浏览器原生方法,现代浏览器原生方法structuredClone无法克隆某些特殊类型的对象(响应式代理对象(reactive/proxy)、函数或方法、Symbol 类型的属性、循环引用、特殊的 DOM 对象),因为emptyCapitalInfo是原始对角不是响应式对象,所以不需要toRaw()
  // capitalInfoStore.setCurrentSelectedCapitalInfo(JSON.parse(JSON.stringify(emptyCapitalInfo))); // ✅深拷贝(推荐):
  store.setCurrentCapitalInfo(cloneDeep(emptyCapitalInfo)); // ✅深拷贝(专业):使用第三方库 lodash/lodash-es 的 cloneDeep,功能全面、可靠、保留更多类型

  infoDialogVisible.value = true;
};

// 信息对话框 - 重置
const handleInfoReset = async () => {
  formData = new FormData();

  // 重置表单数据,方案2: 手动设置默认值(推荐,因为可以精确控制每个字段的默认值)
  // Object.assign(localCapitalInfo, emptyCapitalInfo); // ❌浅拷贝:直接引用源对象,修改源数据可能影响目标,修改localCapitalInfo会影响sourceData,从而影响emptyCapitalInfo
  // Object.assign(localCapitalInfo, { ...emptyCapitalInfo }); // ✅浅拷贝:创建新对象,与源对象解耦,与源数据隔离,更安全,修改localCapitalInfo不会影响emptyCapitalInfo
  // Object.assign(localCapitalInfo, structuredClone(toRaw(emptyCapitalInfo))); // ✅深拷贝:现代浏览器原生方法structuredClone无法克隆某些特殊类型的对象(响应式代理对象(reactive/proxy)、函数或方法、Symbol 类型的属性、循环引用、特殊的 DOM 对象)
  // Object.assign(localCapitalInfo, JSON.parse(JSON.stringify(emptyCapitalInfo))); // ✅深拷贝(推荐):因为localCapitalInfo为reactive对象,不能直接赋值,如果是ref对象,可以 localCapitalInfo.value = JSON.parse(JSON.stringify(emptyCapitalInfo));
  Object.assign(localCapitalInfo, cloneDeep(emptyCapitalInfo)); // ✅深拷贝(专业):使用第三方库 lodash/lodash-es 的 cloneDeep,功能全面、可靠、保留更多类型

  await nextTick();
};

// 监视信息对话框状态,同步数据
watch(infoDialogVisible, async (newValue) => {
  if (newValue) {
    // 对话框打开时,从 store 复制数据到本地副本
    const sourceData = store.currentCapitalInfo || emptyCapitalInfo;
    // Object.assign(localCapitalInfo, sourceData); // ❌浅拷贝:直接引用源对象,修改源数据可能影响目标,修改 localCapitalInfo 会影响 sourceData,从而影响 emptyCapitalInfo
    // Object.assign(localCapitalInfo, { ...sourceData }); // ✅浅拷贝:创建新对象,与源对象解耦,与源数据隔离,更安全,修改 localCapitalInfo 不会影响 sourceData 和 emptyCapitalInfo
    // Object.assign(localCapitalInfo, structuredClone(toRaw(sourceData))); // ✅深拷贝:现代浏览器原生方法 structuredClone 无法克隆某些特殊类型的对象(响应式代理对象(reactive/proxy)、函数或方法、Symbol 类型的属性、循环引用、特殊的 DOM 对象),因为 sourceData 有可以是响应式对象,所以需要使用 toRaw() 剥离响应式代理,获取原始对象
    // Object.assign(localCapitalInfo, JSON.parse(JSON.stringify(sourceData))); // ✅深拷贝(推荐):因为 localCapitalInfo 为 reactive 对象,不能直接赋值,如果是 ref 对象,可以 localCapitalInfo.value = JSON.parse(JSON.stringify(sourceData));
    Object.assign(localCapitalInfo, cloneDeep(sourceData)); // ✅深拷贝(专业):使用第三方库 lodash/lodash-es 的 cloneDeep,功能全面、可靠、保留更多类型

    await nextTick();
  }
});

JavaScript 对象复制:浅拷贝与深拷贝使用说明

在 JavaScript 中,对象复制分为 浅拷贝 和 深拷贝,两者的核心区别在于是否递归复制嵌套的引用类型属性。以下是详细说明和示例:


一、浅拷贝(Shallow Copy)

特点:仅复制对象的第一层属性,若属性是引用类型(如对象、数组),则拷贝的是引用地址。修改嵌套的引用属性会影响原对象。

常用方法:
  1. Object.assign()

    javascript

    const obj = { a: 1, b: { c: 2 } };
    const shallowCopy = Object.assign({}, obj);
    shallowCopy.b.c = 99; // 原对象的 obj.b.c 也会变为 9
    或者
    const shallowCopy = {};
    Object.assign(shallowCopy, obj);
    
    也可以复制 reactive 或 ref 创建的响应式对象
    const refObj = ref({});
    Object.assign(refObj.value, proxyObj);
    
  2. 展开运算符 ...

    javascript

    const obj = { a: 1, b: { c: 2 } };
    const shallowCopy = { ...obj };
  3. 数组浅拷贝

    javascript

    const arr = [1, 2, { d: 3 }];
    const newArr = arr.slice(); // 或 [...arr], Array.from(arr)
适用场景:
  • 对象属性无嵌套引用类型。

  • 需要快速复制且性能敏感的场景。


二、深拷贝(Deep Copy)

特点:完全复制对象及其所有嵌套属性,新旧对象完全独立,互不影响。

常用方法:
  1. JSON.parse(JSON.stringify())

    javascript

    const obj = { a: 1, b: { c: 2 }, d: new Date() };
    const deepCopy = JSON.parse(JSON.stringify(obj));
    
    如果是 reactive 或 ref 创建的响应式对象,先通过 toRaw 转为 普通对象,再进行拷贝。
    例如:
    const deepCopy = JSON.parse(JSON.stringify(toRaw(proxyObj))); // deepCopy 为普通对象
    const deepCopy = reactive(JSON.parse(JSON.stringify(toRaw(proxyObj)))); // deepCopy 为 reactive 响应式对象 Proxy(Object)
    const deepCopy = ref(JSON.parse(JSON.stringify(toRaw(proxyObj)))); // deepCopy 为ref响应式对象 RefImpl
    

    局限性

    • 忽略 undefined、函数、Symbol。

    • 破坏特殊对象(如 Date 转为字符串,RegExp 转为空对象)。

    • 无法处理循环引用。

  2. structuredClone()(现代浏览器/Node.js 17+)

    javascript

    const obj = { a: 1, b: { c: 2 } };
    const deepCopy = structuredClone(obj); // 支持更多类型,如 Date、Set、Map
    
    如果是 reactive 或 ref 创建的响应式对象,先通过 toRaw 转为 普通对象,再进行拷贝。
    例如:
    const deepCopy = structuredClone(toRaw(proxyObj)); // deepCopy 为普通对象
    const deepCopy = reactive(structuredClone(toRaw(proxyObj))); // deepCopy 为 reactive 响应式对象 Proxy(Object)
    const deepCopy = ref(structuredClone(toRaw(proxyObj))); // deepCopy 为ref响应式对象 RefImpl
    
  3. Lodash 的 cloneDeep()

    npm install lodash.clonedeep 
    import cloneDeep from 'lodash.clonedeep';
    localApplySampleList.value = reactive(cloneDeep(rawData));  

    javascript

    import { cloneDeep } from 'lodash';
    const obj = { a: 1, b: { c: 2 } };
    const deepCopy = cloneDeep(obj); // 处理复杂对象(函数、循环引用等)
  4. 手动递归实现

    javascript

    function deepClone(source, map = new WeakMap()) {
      if (source === null || typeof source !== 'object') return source;
      if (map.has(source)) return map.get(source); // 解决循环引用
      const target = Array.isArray(source) ? [] : {};
      map.set(source, target);
      for (const key in source) {
        if (source.hasOwnProperty(key)) {
          target[key] = deepClone(source[key], map);
        }
      }
      return target;
    }
适用场景:
  • 对象包含多层嵌套引用。

  • 需要完全独立副本(如状态管理、撤销操作)。


三、如何选择?

方法类型优点缺点
Object.assign浅拷贝简单快速无法处理嵌套引用
JSON 序列化深拷贝原生支持丢失特殊类型,无法处理循环引用
structuredClone深拷贝支持更多类型兼容性要求(IE 不支持)
Lodash cloneDeep深拷贝功能全面需引入第三方库

四、注意事项

  1. 循环引用:手动实现或第三方库需处理对象间的循环引用。

  2. 特殊类型:根据数据类型选择方法(如 structuredClone 支持 Date、Map 等)。

  3. 性能:深拷贝对大型对象有较高开销,优先考虑浅拷贝。


总结

  • 浅拷贝:用 Object.assign 或 ...,适合简单对象。

  • 深拷贝:优先使用 structuredClone 或 Lodash,复杂场景手动实现需谨慎。

通过合理选择拷贝方式,可避免数据意外修改,提升代码健壮性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值