代码复制术:浅拷贝与深拷贝知多少?

在 JavaScript 开发中,数据拷贝是常见需求,尤其是对象或数组的复制。理解浅拷贝深拷贝的区别,能帮助我们避免常见错误。

一、浅拷贝

1.1 什么是浅拷贝?

浅拷贝创建一个新对象/数组,顶层属性值与原数据相同,但对于引用类型属性(如对象、数组),新数据与原数据共享内存引用。即:

  • 基本类型(如 numberstring):值复制。
  • 引用类型(如 objectarray):仅复制引用地址,而非实际对象。

1.2 浅拷贝的实现方式

(1) Object.assign()

将源对象的可枚举属性浅拷贝到目标对象,嵌套对象为引用共享。

let obj = { a: 1, b: { n: 2 } };
let copy = Object.assign({}, obj);
obj.b.n = 200;
console.log(copy.b.n); // 输出 200(共享引用)

(2) Object.create()

基于原型创建新对象,新对象的隐式原型为原对象。

let obj = { a: 1, b: { n: 2 } };
let copy = Object.create(obj); // 创建隐式原型为obj的空对象
copy.a = 10; // {a: 10}
obj.b.n = 1;
console.log(copy.a); // 输出 10(自身属性)
console.log(copy.b); // 输出 { n: 1 }(引用共享)

(3) 展开运算符 ...

展开对象属性到新对象,嵌套对象为引用共享。

let obj = { a: 1, b: { n: 2 } };
let copy = { ...obj };
obj.b.n = 200;
console.log(copy.b.n); // 输出 200

(4) 数组的 slice()concat()

创建新数组,元素为浅拷贝,引用类型共享。

// slice() 示例
let arr = [1, 2, { n: 3 }];
let copy = arr.slice();
arr[2].n = 300;
console.log(copy[2].n); // 输出 300(共享对象)

// concat() 示例
let arr = [1, 2, 3, { a: 1 }];
let copy = arr.concat();
arr[3].a = 200;
console.log(copy[3].a); // 输出 200(共享对象)

1.3 浅拷贝的风险

  • 引用共享导致数据污染:修改拷贝对象的引用类型属性,会影响原对象。
  • 隐式原型拷贝:可能复制对象的隐式原型(如通过 Object.create())。

二、深拷贝

2.1 什么是深拷贝?

递归拷贝对象的所有层级数据,包括嵌套的对象/数组,新对象与原对象完全独立,修改互不影响。

2.2 深拷贝的实现方式

(1) JSON.parse(JSON.stringify(obj))

通过 JSON 序列化/反序列化实现,简单但有诸多限制。

let obj = { a: 1, b: { n: 2 } };
let copy = JSON.parse(JSON.stringify(obj));
obj.b.n = 200;
console.log(copy.b.n); // 输出 2(完全独立)


限制

  • 无法处理 undefinedsymbolbigintDateNaN 等特殊类型。
  • 会丢失对象的原型链。
  • 不能复制函数、正则表达式。

(2) 递归拷贝(手动实现)

通过递归遍历对象,逐层复制属性,处理引用类型。

function deepClone(obj) {
  if (typeof obj!== 'object' || obj === null) {
    return obj; // 基本类型或 null 直接返回
  }
  // 判断是否为数组,创建对应类型的新对象
  const newObj = Array.isArray(obj)? [] : {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = deepClone(obj[key]); // 递归拷贝子属性
    }
  }
  return newObj;
}

let obj = { a: 1, b: { n: 2 } };
let copy = deepClone(obj);
obj.b.n = 200;
console.log(copy.b.n); // 输出 2(深拷贝成功)

(3) structuredClone()(原生方法)

浏览器提供的原生深拷贝方法,支持复制 MapSetDateArrayBuffer 等复杂类型。

let obj = { a: 1, b: { n: 2 } };
let copy = structuredClone(obj);
obj.b.n = 200;
console.log(copy.b.n); // 输出 2(深拷贝成功)


注意:兼容性较差,旧版浏览器可能不支持。

(4) MessageChannel(消息通道实现)

通过消息传递实现深拷贝,适用于特定场景(一般不常用)。

function deepClone(obj) {
  return new Promise((resolve) => {
    const { port1, port2 } = new MessageChannel();
    port1.postMessage(obj); // 通过通道发送数据
    port2.onmessage = (ev) => resolve(ev.data); // 接收拷贝后的数据
  });
}

let obj = { a: 1, b: { n: 2 } };
deepClone(obj).then((copy) => {
  obj.b.n = 200;
  console.log(copy.b.n); // 输出 2(深拷贝成功)
});

2.3 深拷贝的优缺点

  • 优点:新对象与原对象完全独立,修改无副作用。
  • 缺点
  • 性能开销大(尤其嵌套层级深时)。
  • 需要手动处理特殊数据类型(如函数、原型链)。

三、总结对比

特性浅拷贝深拷贝
复制层级仅顶层属性,引用类型共享内存递归复制所有层级,完全独立
适用场景简单数据、性能优先复杂对象、需要数据隔离
典型方法Object.assign()、展开运算符递归函数、JSON.stringify()
风险引用共享导致数据联动性能损耗、特殊类型处理复杂

选择建议

  • 若只需复制基本类型或浅层引用,用浅拷贝(高效)。
  • 若涉及多层嵌套对象且需完全独立,用深拷贝(牺牲性能换隔离)。

理解两者差异,能更精准地处理数据复制需求,避免因引用共享引发的潜在问题。

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注