别再乱拷贝了!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 代码的关键一步。

相关推荐
滑雪的企鹅.14 分钟前
HTML头部元信息避坑指南大纲
前端·html
一拳不是超人17 分钟前
老婆天天吵吵要买塔罗牌,我直接用 AI 2 小时写了个在线塔罗牌
前端·ai编程
excel2 小时前
如何解决 Nuxt DevTools 中关于 unstorage 包的报错
前端
Rust研习社2 小时前
使用 Axum 构建高性能异步 Web 服务
开发语言·前端·网络·后端·http·rust
C澒2 小时前
AI 生码 - API2Code:接口智能匹配与 API 自动化生码全链路设计
前端·低代码·ai编程
浔川python社2 小时前
HTML头部元信息避坑指南技术文章大纲
前端·html
IT_陈寒3 小时前
SpringBoot配置加载顺序把我坑惨了
前端·人工智能·后端
kyriewen3 小时前
Next.js部署:从本地跑得欢,到线上飞得稳
前端·react.js·next.js
Moment3 小时前
面试官:给 llm 传递上下文,有哪几个身份 role ❓❓❓
前端·后端·面试
跨境数据猎手3 小时前
跨境独立站系统技术拆解(附带源码)
服务器·前端·php