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

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

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

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

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

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

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

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

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

核心特点

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

常用实现方式

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

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

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

    ini 复制代码
    const newArr = [].concat(arr);

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

  • toReversed()reverse() 方法

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

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

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

    核心语法

css 复制代码
Object.assign(target, ...sources)

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

代码示例:

ini 复制代码
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 字符串,再反序列化为新对象。

ini 复制代码
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+ 支持,是更标准的深拷贝方案。

ini 复制代码
const newObj = structuredClone(obj);

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

四、总结

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

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

相关推荐
kyriewen1 小时前
Sass 进阶:当 CSS 学会了编程,变量函数循环全都安排上
前端·css·scss
海带先森2 小时前
python 虚拟环境的创建
前端
lovemiss2 小时前
解码本质:claude code是怎么运行的
前端
yuxi20202 小时前
Cursor 的 7 个隐藏功能,90% 的人不知道
前端
Moment2 小时前
MiniMax 发布 M2.7,Agent 开始走向自我进化
前端·后端·面试
发现一只大呆瓜2 小时前
Vue-Vue Router核心原理+实战用法全解析
前端·vue.js·面试
m0_694845572 小时前
Oh My Zsh 使用指南:Zsh 终端配置与插件管理教程
服务器·前端·小程序·开源·github
英俊潇洒美少年2 小时前
React19 useActionState的注意事项
前端·javascript·react.js
huaqianzkh2 小时前
两个 ASP.NET Core Web API 模板核心区别
前端·后端·asp.net