JavaScript 中的浅拷贝与深拷贝全面解析
JavaScript 中的浅拷贝与深拷贝全面解析
在实际开发中,“修改一个对象,另一个对象也跟着变了”是非常常见的坑,其根源就在于:引用类型的赋值是“引用传递”。理解浅拷贝与深拷贝,是解决这类问题的关键。
本文将从以下几个方面系统讲清楚:
- 基本类型 vs 引用类型在内存中的差异
- 什么是浅拷贝?有哪些常见实现方式?
- 什么是深拷贝?
JSON.parse(JSON.stringify())有哪些坑? - 如何手写一个相对实用的深拷贝函数?
- 在工程中如何选择合适的拷贝方式?
一、值类型与引用类型的赋值差异
1. 基本类型(按值传递)
1 | |
对 b 的修改不会影响 a。
2. 引用类型(按引用传递)
1 | |
这里 obj1 和 obj2 指向的是同一个对象,所以任何一方的修改都会体现在另一方上。
为了解决这种“互相影响”的问题,就引出了浅拷贝与深拷贝。
二、浅拷贝:只拷贝第一层
定义:
浅拷贝会创建一个新对象,将原对象的第一层属性复制过去。
若属性值是引用类型,则拷贝的是“引用地址”,两者仍指向同一个子对象。
1. 常见浅拷贝方式
(1)Object.assign
1 | |
(2)展开运算符 …
1 | |
效果与 Object.assign({}, obj) 相同:都是浅拷贝。
(3)数组的 slice / concat
1 | |
对数组元素本身的修改互不影响,但对元素内部对象的修改会互相影响。
2. 适用场景
- 对象/数组只有一层结构;
- 或你只关心浅层属性,深层属性本身会被整体替换。
例如在 Redux 中,常常通过:
1 | |
来创建新状态对象;若某个字段是大对象,通常会整体替换,以避免深层共享引用带来的 bug。
三、深拷贝:复制整个对象图
定义:
深拷贝会递归复制对象的所有层级,生成一个与原对象“结构相同但引用完全独立”的新对象。
修改新对象的任何嵌套属性,都不会影响到原对象。
1. 最常见的“快捷方式”:JSON.parse(JSON.stringify())
1 | |
看起来很好用,但存在诸多限制:
- 无法处理:
undefinedSymbolFunctionDate(会变成字符串)RegExp(会变成空对象)Map/Set/WeakMap/WeakSet- 循环引用(会直接报错)
示例:
1 | |
因此,JSON 方案只适用于:
- 数据结构简单、只包含普通对象/数组/数字/字符串/布尔值/null 的情况;
- 并且可以接受对 Date 等类型的“降级”。
四、手写一个相对实用的深拷贝函数
下面实现一个简单但实用性较高的 deepClone:
- 支持:
Object、Array、Date、RegExp、Map、Set - 能处理循环引用
- 不复制原型链上的属性(只复制自有属性)
1 | |
1. 处理循环引用
1 | |
如果不使用 WeakMap 记录已克隆过的对象,这种结构会导致无限递归。
五、工程实践中的选择建议
1. 优先考虑“不可变数据结构”的设计
在很多场景下,与其到处做深拷贝,不如在设计阶段:
- 让数据结构尽量“扁平化”
- 尽量避免深层嵌套
- 通过 ID 引用关联数据,而不是嵌套层层对象
这样可以:
- 降低拷贝成本
- 提高 diff/比较效率(如 Redux/Vue/React 的更新逻辑)
2. 选择合适拷贝方式
| 场景 | 推荐方式 |
|---|---|
| 浅层对象、只关心第一层 | Object.assign 或 展开运算符 |
| 简单数据(只含 JSON 兼容类型) | JSON.parse(JSON.stringify()) |
| 复杂嵌套结构、包含 Date/Map/Set | 使用手写 deepClone 或专业库(如 lodash 的 cloneDeep) |
| 性能敏感、数据更新频繁 | 考虑不拷贝大对象,而是通过不可变数据模式、结构共享等优化 |
3. 注意性能与“过度复制”
深拷贝是有成本的:
- 递归遍历整个对象图
- 创建大量新对象与数组
在大型数据结构上频繁深拷贝,可能带来明显的性能问题,应尽量避免:
- 在频繁更新的热路径上做深拷贝
- 在动画帧、高频事件中深拷贝大对象
六、总结
浅拷贝与深拷贝的本质区别在于:
- 浅拷贝:只复制一层,对象内部的引用类型属性仍指向同一个对象
- 深拷贝:递归复制所有层级,生成一个结构完全相同但引用完全独立的新对象
日常开发中:
- 学会区分哪种场景只需要浅拷贝,哪种场景必须深拷贝
- 了解
JSON.parse(JSON.stringify())的适用边界与坑 - 在需要更强能力时,使用手写
deepClone或成熟的第三方库
有了这些基础,再结合不可变数据模式与状态管理库(Redux/MobX/Pinia 等),你就能更加自信地处理复杂状态与数据结构的变更,而不必担心“改了这边,那边也跟着变”的诡异问题。
JavaScript 中的浅拷贝与深拷贝全面解析
https://sunjc.vip/2024/03/27/浅拷贝与深拷贝全面解析/