JavaScript 浅拷贝与深拷贝的实现原理及方案
在 JavaScript 中,处理对象和数组的复制时,理解浅拷贝(Shallow Copy)与深拷贝(Deep Copy)的区别至关重要。两者的核心差异在于对引用类型属性的处理方式。
1. 浅拷贝的机制与常见实现
浅拷贝会创建一个新对象,并将原始对象的属性值精确拷贝一份。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型(如对象或数组),则拷贝的是内存地址。这意味着新旧对象会共享同一块内存空间,修改其中一个对象的引用属性会影响另一个。
1.1 使用 Object.assign
Object.assign 是 ES6 提供的常用方法,它可以将一个或多个源对象的可枚举属性复制到目标对象。
const sourceModel = { id: 1, info: { name: "Tech" } };
const clonedModel = Object.assign({}, sourceModel);
// 注意:它不会拷贝原型链上的属性,也不会拷贝不可枚举属性。
1.2 扩展运算符 (...)
对象的扩展运算符提供了更简洁的语法来实现浅拷贝,其表现与 Object.assign 基本一致。
const original = { a: 1, b: { c: 2 } };
const copy = { ...original };
1.3 数组的浅拷贝方法
对于数组类型,可以通过以下方法快速生成浅拷贝副本:
Array.prototype.slice():通过arr.slice()不传参数获取新数组。Array.prototype.concat():通过[].concat(arr)连接空数组。
1.4 使用属性描述符克隆
如果需要保留对象的原型并获取完整的属性描述符,可以使用 Object.getOwnPropertyDescriptors 配合 Object.create。
const smartClone = (origin) => Object.create(
Object.getPrototypeOf(origin),
Object.getOwnPropertyDescriptors(origin)
);
2. 深拷贝的实现方案
深拷贝会递归地复制对象及其所有层级的嵌套对象。拷贝完成后,新对象与原对象在内存中完全独立。
2.1 JSON 序列化(快捷但有限制)
利用 JSON.stringify 将对象转为字符串,再用 JSON.parse 还原。这是一种简单高效的方法,但存在以下局限性:
- 会忽略
undefined、Symbol和函数。 RegExp和Error对象会被转为空对象。- 无法处理循环引用(Circular Reference)。
Set、Map等特殊数据结构会被转换成空对象{}。
const complexData = {
active: true,
tags: [1, 2],
meta: { author: "Admin" }
};
const deepResult = JSON.parse(JSON.stringify(complexData));
2.2 递归实现(通用方案)
为了处理复杂的数据结构、循环引用以及特殊的内建对象(如 Map 和 Set),通常需要编写一个递归函数。通过 WeakMap 来存储已拷贝的对象引用,可以有效解决循环引用导致的栈溢出问题。
function performDeepClone(target, cache = new WeakMap()) {
// 处理基本类型及 null
if (target === null || typeof target !== 'object') {
return target;
}
// 防止循环引用
if (cache.has(target)) {
return cache.get(target);
}
let result;
// 处理特殊引用类型
if (Array.isArray(target)) {
result = [];
} else if (target instanceof Set) {
result = new Set();
target.forEach(val => result.add(performDeepClone(val, cache)));
return result;
} else if (target instanceof Map) {
result = new Map();
target.forEach((val, key) => result.set(key, performDeepClone(val, cache)));
return result;
} else {
result = {};
}
// 记录当前对象缓存
cache.set(target, result);
// 递归处理子属性
Object.keys(target).forEach(key => {
result[key] = performDeepClone(target[key], cache);
});
return result;
}
// 示例:处理循环引用
const nodeA = { name: 'Node A' };
nodeA.self = nodeA;
const clonedNode = performDeepClone(nodeA);
console.log(clonedNode !== nodeA); // true
console.log(clonedNode.self === clonedNode); // true
在生产环境中,如果对性能和边界情况有极高要求,建议使用成熟的第三方库如 lodash.cloneDeep,或者在现代浏览器中使用原生 API structuredClone()。