一、简介

1、什么是深浅拷贝

        A deep copy of an object is a copy whose properties do not share the same references (point to the same underlying values) as those of the source object from which the copy was made. As a result, when you change either the source or the copy, you can be assured you're not causing the other object to change too. That behavior contrasts with the behavior of a shallow copy, in which changes to nested properties in the source or the copy may cause the other object to change too.

        对象的深拷贝是指其属性与其拷贝的源对象的属性不共享相同的引用(指向相同的底层值)的副本。因此,当你更改源或副本时,可以确保不会导致其他对象也发生更改;也就是说,你不会无意中对源或副本造成意料之外的更改。这种行为与浅拷贝的行为形成对比,在浅拷贝中,对源或副本的更改可能也会导致其他对象的更改(因为两个对象共享相同的引用)。

深拷贝 - MDN Web 文档术语表:Web 相关术语的定义 | MDN

        在 JavaScript 中,深浅拷贝是针对引用类型(对象、数组等)的复制操作,核心区别在于是否 “复制引用地址” 或 “复制对象本身”。

const original = { a: 1, b: { c: 2 } };
const copy = original; // 这不是拷贝!

copy.b.c = 20; // 修改副本...

//// ...原始数据也被污染
console.log(original.b.c); // 20
  • 这里 copy = original 只是复制了引用地址,copy 和 original 指向堆中同一个对象
  • 因此修改 copy.b.c 会同步污染 original 的数据 —— 这不是拷贝,只是引用共享

2、为什么要拷贝?

  1. 避免意外的副作用:修改副本时,不污染原始数据
  2. 实现状态的不可变性:React/Vue 等框架的核心机制
  3. 隔离数据:创建完全独立的数据快照(某一时刻数据的 “静态副本” ——只读、独立、时间点标记)

二、浅拷贝(SHALLOW COPY)

        只复制引用地址的副本,新对象与原对象共享堆内存中的同一份数据。

  • 创建一个新对象
  • 复制原始对象的 第一层 属性
  • 如果属性是值类型,复制 值
  • 如果属性是引用类型,复制 内存地址

特点:

  • 对基本类型属性:复制值的副本(相互独立)。
  • 对引用类型属性:复制引用地址的副本(共享堆中数据,修改一方会影响另一方)。

实现方式:

  1. Object.assign()

    Object.assign(target, ...sources) //sources:一个或多个包含要应用的属性的源对象

            静态方法将一个或者多个源对象中所有可枚举自有属性复制到目标对象,并返回修改后的目标对象。 可以合并多个对象(支持多个源对象)

    const original = { a: 1, b: { c: 2 } };
    const shallowCopy = Object.assign({}, original);
    shallowCopy.a = 10;    // 修改第一层
    shallowCopy.b.c = 20;  // 修改嵌套对象
    
    console.log(original.a);     // 1(未受影响)
    console.log(original.b.c);   // 20(受到影响!)
    
    const target = { a: 1, b: 2 };
    const source = { b: 4, c: 5 };
    const returnedTarget = Object.assign(target, source);
    
    console.log(target);  //{ a: 1, b: 4, c: 5 }
    console.log(returnedTarget === target); //true why?返回修改后的目标对象!
    
  2. 扩展运算符 ... 

            更简洁,适合单个对象拷贝或简单合并。与Object.assign()一样,对原型链属性的处理一致:都复制原型链属性

    const arr = [1, { d: 2 }];
    const shallowArr = [...arr];
    shallowArr[1].d = 3;
    console.log(arr[1].d); // 3(原数组被修改)
    
    const original = { a: 1, b: { c: 2 } };
    const shallowCopy = { ...original };
    shallowCopy.a = 10;
    shallowCopy.b.c = 20;
    console.log(original.a);     // 1(未受影响)
    console.log(original.b.c);   // 20(受到影响!)
    

三、深拷贝(DEEP COPY)

        深拷贝会递归复制所有层级的属性,新旧对象在堆中完全独立:无论值类型还是引用类型,都会复制 “值或对象本身”,修改一方不会影响另一方。

  • 创建一个新对象
  • 递归地 复制所有层级的属性
  • 所有引用类型属性都会被 重新创建
  • 新旧对象 完全独立,互不干扰

特点:

        无论基本类型还是引用类型属性,都复制值或对象本身,完全脱离原对象的引用关系。

实现方式:

1、JSON.parse(JSON.stringify())(简单场景适用)

优点:
  • 极简,一行代码
  • 处理纯 JSON 数据时很方便
const obj = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj));
deepCopy.b.c = 3;
console.log(obj.b.c); // 2(原对象不受影响)
缺点:
  • 忽略 undefined
  • 忽略 Symbol
  • 忽略 Function
  • Date 对象会变成字符串
  • 无法处理循环引用 (会报错)

2、structuredClone()

✨现代 WEB API 的推荐选择✨这是现代 Web API 提供的原生深拷贝方法,是处理复杂对象深拷贝的推荐选择。

优点
  • 递归复制所有层级的属性,新旧对象完全独立。
  • 支持处理 DateRegExpMapSet 等特殊对象,还能正确处理循环引用。
const original = {a: 1, b: { c: 2 }, d: new Date() };
const deepCopy = structuredClone(original);
deepCopy.b.c = 20; 
console.log(original.b.c); // 2(原对象不受影响,证明是深拷贝)
console.log(original.d.getTime() === deepCopy.d.getTime()); // true(Date 对象被正确复制,时间值一致)
缺点:
  • 丢失Function
  • Symbol 作为对象属性键时会丢失,作为值时也会丢失
  • undefined 作为属性值时会丢失(转为 null 或直接移除)

总结

  • 若场景是纯 JSON 数据(无特殊类型、循环引用),可临时用 JSON.parse(JSON.stringify())
  • 若需要处理复杂对象(含特殊类型、循环引用),推荐使用 structuredClone(),它是现代 Web 环境下深拷贝的 “最优解”。
const original = {a: 1, b: { c: 2 }, d: new Date() };

const deepCopy = structuredClone(original);

deepCopy.b.c = 20; // 修改副本

console.log(original.b.c); // 2(未受影响)

console.log(original.d.getTime() === deepCopy.d.getTime()); //true

3、自定义递归函数(通用方案)

function deepClone(target, map = new WeakMap()) {
  if (target === null || typeof target !== 'object') return target;
  if (map.has(target)) return map.get(target); // 解决循环引用
  const clone = Array.isArray(target) ? [] : {};
  map.set(target, clone);
  for (let key in target) {
    if (Object.prototype.hasOwnProperty.call(target, key)) {
      clone[key] = deepClone(target[key], map);
    }
  }
  return clone;
}

const obj = { a: 1, b: { c: 2 }, fn: () => {} };
const clone = deepClone(obj);
clone.b.c = 3;
console.log(obj.b.c); // 2(原对象不受影响)
console.log(typeof clone.fn); // 'function'(函数被复制)

四、对比总结

类型复制层级引用类型属性的影响实现复杂度适用场景
浅拷贝只复制第一层修改会同步影响原对象简单只需复制基本类型属性
深拷贝复制所有层级修改不影响原对象复杂需要完全独立的对象副本

1、性能开销

  • 浅拷贝: 快,开销小。
  • 深拷贝: 慢,开销大,需要遍历所有节点。

2、如何选择?

原则: 按需使用,勿滥用深拷贝。

• 仅一层: 浅拷贝 ... 或 Object.assign。

• 需要深拷贝:

  ▪ 首选: structuredClone()

  ▪ 备选 (数据简单): JSON.parse(JSON.stringify())

  ▪ 需要拷函数/复杂场景: lodash.cloneDeep

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐