深拷贝与浅拷贝的区别
深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是对象复制的两种核心方式,其核心区别在于是否复制嵌套对象以及内存独立性:
- 浅拷贝:
- 仅复制对象的第一层属性,嵌套对象(如数组、子对象)仍与原对象共享内存地址。
- 结果:修改新对象的嵌套属性时,原对象的对应属性也会被修改(新旧对象存在关联)。
- 实现方式:
Object.assign()
(仅第一层深拷贝)- 扩展运算符
{...obj}
或[...array]
(仅第一层深拷贝) - 数组的
concat()
、slice()
(仅数组第一层)
- 深拷贝:
- 递归复制对象及其所有嵌套对象,新旧对象完全独立,无共享内存。
- 结果:修改新对象(包括嵌套属性)不会影响原对象。
- 适用场景:对象包含多层嵌套结构(如树形数据),需完全隔离修改。
关键区别总结(表格形式):
特性 | 浅拷贝 | 深拷贝 |
---|---|---|
复制层级 | 仅第一层属性 | 所有层级(递归嵌套对象) |
嵌套对象内存 | 共享内存(修改相互影响) | 独立内存(修改完全隔离) |
原对象是否受影响 | 是(嵌套属性修改时) | 否 |
适用数据类型 | 单层对象、数组 | 多层嵌套对象、复杂结构 |
深拷贝函数的实现方法
以下是常见深拷贝方案的代码实现及注意事项:
1. 递归实现(基础版)
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj; // 基本类型直接返回
}
let clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]); // 递归复制嵌套属性
}
}
return clone;
}
缺点:
- 未处理循环引用(如
obj.self = obj
)导致栈溢出。
2. 解决循环引用的递归实现
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (map.has(obj)) return map.get(obj); // 检测循环引用
let clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone); // 缓存当前对象
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], map); // 递归并传递缓存
}
}
return clone;
}
优化点:
- 使用
WeakMap
跟踪已拷贝对象,避免循环引用导致的死循环。
3. JSON序列化法
const deepClone = obj => JSON.parse(JSON.stringify(obj));
缺点:
- 忽略
undefined
、Symbol
和函数; - 不支持
Date
(转为字符串)、RegExp
(转为空对象)、Map/Set
等特殊类型; - 循环引用直接报错。
适用场景:无特殊类型且无循环引用的简单对象。
4. 第三方库实现(推荐生产环境使用)
-
Lodash:
import _ from 'lodash';
const clonedObj = _.cloneDeep(originalObj); // 支持循环引用及特殊类型
jQuery:
const clonedObj = $.extend(true, {}, originalObj); // 深度递归复制
优势:
- 内置处理循环引用、特殊数据类型(如
Date
、RegExp
)。
深拷贝的注意事项
- 循环引用:
- 递归实现需通过缓存(如
WeakMap
)避免无限递归。
- 递归实现需通过缓存(如
- 特殊数据类型:
Date
、RegExp
、Map
、Set
等需单独处理(如new Date(obj.getTime())
)。
- 性能问题:
- 递归深拷贝大对象可能消耗较多内存/时间,JSON方法在大型数据上较慢。
- 不可变对象:
- 如
String
、Number
等不可变类型无需深拷贝。
- 如
方法选择建议:
场景 | 推荐方法 |
---|---|
简单对象(无嵌套、无特殊类型) | JSON序列化 |
含循环引用或特殊类型 | 递归+WeakMap 或 Lodash |
生产环境复杂对象 | Lodash(_.cloneDeep) |
完整深拷贝应覆盖所有边界情况,建议优先使用成熟的第三方库(如 Lodash),避免重复造轮子及潜在风险。