阅读视图

发现新文章,点击刷新页面。

别再乱拷贝了!JS 浅拷贝 vs 深拷贝全解析

在 JavaScript 开发中,对象拷贝是一个绕不开的核心话题。无论是状态管理、数据缓存还是函数参数传递,我们都需要谨慎处理数据的复制方式,避免因引用共享导致意外的数据修改。

本文将结合实际开发场景,详细拆解浅拷贝与深拷贝的区别、实现方式及适用场景。

一、拷贝的本质:引用 vs 新对象

JavaScript中的对象(包括数组、函数等)属于引用类型,变量存储时存储的并非是对象本身,而是对象的引用地址

  • 原始类型拷贝:直接复制值,两个变量互不影响。
  • 引用类型拷贝:如果只是简单赋值(const newObj = obj),本质是复制了对象的引用地址,新旧对象指向同一块内存,修改其中一个会直接影响另一个。

真正的 “拷贝”,是基于原对象创建一个新对象,使新对象与原对象在内存上相互独立。根据拷贝的深度,又分为浅拷贝深拷贝

二、浅拷贝:只复制第一层

浅拷贝(Shallow Copy)只会复制对象的第一层属性,如果属性值是引用类型(如子对象、数组),则仍然复制其引用地址。

核心特点

  • 新对象的第一层属性与原对象隔离。
  • 嵌套的子对象 / 数组仍共享引用,修改子对象会影响原对象

常用实现方式

1. 数组专用方法
  • Array.prototype.slice(0) :创建原数组的浅拷贝。

    const arr = [1, 2, { a: 3 }];
    const newArr = arr.slice(0);
    newArr[2].a = 4; // 会修改原数组的 arr[2].a
    
  • 扩展运算符 ... :ES6 新增,语法更简洁。

    const newArr = [...arr];
    
  • Array.prototype.concat() :合并数组并返回新数组。

    const newArr = [].concat(arr);
    

在一个空数组后拼接原数组并赋值给新数组,这个新数组就可以说是由原数组拷贝所得到的。

  • toReversed()reverse() 方法

toReversed() 反转数组,得到一个新数组reverse() 反转数组,改变原数组。通过这两个方法组合,我们就可以实现浅拷贝的效果。

const newArr=arr.toReversed().reverse()
2. 对象通用方法
  • Object.assign({}, obj) :将原对象的可枚举属性复制到新对象。

    const obj = { a: 1, b: { c: 2 } };
    const newObj = Object.assign({}, obj);
    newObj.b.c = 3; // 原对象 obj.b.c 也会变为 3
    
  • Object.assign():是 JavaScript 中用于对象属性复制与合并的核心方法,它能将一个或多个源对象可枚举属性复制到目标对象中,并返回修改后的目标对象。

    核心语法

Object.assign(target, ...sources)

target是接受属性的目标修改对象,...sources是一个或多个提供属性的对象

代码示例:

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 }; 
Object.assign(target, source); 
console.log(target); // { a: 1, b: 4, c: 5 }

若目标对象与源对象存在同名属性,后面的源对象属性会覆盖前面的

三、深拷贝:彻底隔离数据

深拷贝(Deep Copy)会递归复制对象的所有层级,包括嵌套的子对象、数组等,最终得到一个与原对象完全独立的新对象,修改新对象不会对原对象产生任何影响。

核心特点

  • 新对象与原对象在内存上完全隔离。
  • 无论修改哪一层属性,都不会影响对方。

常用实现方式

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

这是最常用的 “民间” 深拷贝方案,先将对象序列化为 JSON 字符串,再反序列化为新对象。

const obj = { a: 1, b: { c: 2 } };
const newObj = JSON.parse(JSON.stringify(obj));
newObj.b.c = 3; // 原对象不受影响

局限性:无法处理函数、SymbolBigIntundefinedNaNInfinityfunction 等特殊类型,且会丢失原型链。

2. structuredClone()

浏览器原生 API,现代浏览器和 Node.js 17+ 支持,是更标准的深拷贝方案。

const newObj = structuredClone(obj);

局限性:无法拷贝函数、Symbol,也不能处理带有循环引用的对象。

四、总结

  • 浅拷贝:高效、轻量,适合处理扁平结构数据,但要注意嵌套引用的问题。
  • 深拷贝:彻底隔离数据,避免副作用,但性能开销更大。
  • 核心原则:根据数据结构和业务场景选择合适的拷贝方式,避免过度设计。

在实际开发中,我们应优先使用浅拷贝保证性能,只有在数据结构复杂且需要完全隔离时,才考虑深拷贝。理解拷贝的本质,是写出健壮、可维护的 JavaScript 代码的关键一步。

❌