深拷贝与浅拷贝
约 2279 字大约 8 分钟
2026-04-16
1. 基本概念
1.1 赋值操作
赋值操作是将原对象的引用复制给新变量,两个变量指向同一个对象,修改其中一个会影响另一个。
const obj1 = { name: 'zzy', age: 18 };
const obj2 = obj1;
obj2.age = 20;
console.log(obj1.age); // 20,两者互相影响
console.log(obj1 === obj2); // true,指向同一对象1.2 浅拷贝
浅拷贝会创建一个新对象,然后复制原对象的所有属性。如果属性是基本类型,拷贝的是值;如果属性是引用类型(对象、数组),拷贝的是引用地址。
const obj1 = {
name: 'zzy',
age: 18,
hobby: ['reading', 'coding']
};
const obj2 = Object.assign({}, obj1);
obj2.name = 'changed';
obj2.hobby.push('gaming');
console.log(obj1.name); // 'zzy',基本类型不受影响
console.log(obj1.hobby); // ['reading', 'coding', 'gaming'],嵌套数组被修改
console.log(obj1 === obj2); // false,不同对象1.3 深拷贝
深拷贝会递归复制对象的所有层级,包括嵌套的对象和数组,生成完全独立的新对象。
const obj1 = {
name: 'zzy',
age: 18,
hobby: ['reading', 'coding']
};
const obj2 = JSON.parse(JSON.stringify(obj1));
obj2.name = 'changed';
obj2.hobby.push('gaming');
console.log(obj1.name); // 'zzy'
console.log(obj1.hobby); // ['reading', 'coding'],完全不受影响
console.log(obj1 === obj2); // false,完全独立1.4 三者区别对比
| 特性 | 赋值操作 | 浅拷贝 | 深拷贝 |
|---|---|---|---|
| 是否创建新对象 | 否 | 是 | 是 |
| 是否复制嵌套对象 | 否,共享引用 | 否,共享引用 | 是,独立复制 |
| 修改是否影响原对象 | 是 | 嵌套属性会 | 否 |
const original = {
a: 1,
b: {
c: 2,
d: [3, 4]
}
};
// 赋值
const assigned = original;
assigned.a = 10;
assigned.b.c = 20;
console.log(original.a); // 10
console.log(original.b.c); // 20
// 浅拷贝
const shallow = Object.assign({}, original);
shallow.a = 10;
shallow.b.c = 20;
console.log(original.a); // 1
console.log(original.b.c); // 20
// 深拷贝
const deep = JSON.parse(JSON.stringify(original));
deep.a = 10;
deep.b.c = 20;
console.log(original.a); // 1
console.log(original.b.c); // 22. 浅拷贝的实现方式
2.1 Object.assign()
Object.assign() 方法用于将所有可枚举的自有属性从一个或多个源对象复制到目标对象。
const target = { a: 1 };
const source = { b: 2, c: 3 };
const result = Object.assign(target, source);
console.log(result); // { a: 1, b: 2, c: 3 }
console.log(target === result); // true2.2 展开运算符
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { ...obj1 };
obj2.b.c = 3;
console.log(obj1.b.c); // 3,嵌套对象仍是浅拷贝2.3 Array.slice()
slice() 方法返回一个新数组,不会修改原数组。对于一维数组是深拷贝,对于嵌套数组仍是浅拷贝。
const arr1 = [1, 2, [3, 4]];
const arr2 = arr1.slice();
arr2[0] = 10;
arr2[2][0] = 30;
console.log(arr1[0]); // 1
console.log(arr1[2][0]); // 30,嵌套数组被修改2.4 Array.concat()
concat() 方法用于合并数组,不会修改原数组。对于一维数组是深拷贝,对于嵌套数组仍是浅拷贝。
const arr1 = [1, 2, [3, 4]];
const arr2 = arr1.concat();
arr2[2][0] = 30;
console.log(arr1[2][0]); // 30,嵌套数组被修改2.5 Array.from()
Array.from() 从类数组或可迭代对象创建一个新数组。
const arr1 = [1, 2, [3, 4]];
const arr2 = Array.from(arr1);
arr2[2][0] = 30;
console.log(arr1[2][0]); // 30,嵌套数组被修改2.6 手写浅拷贝函数
function shallowClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
const clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = obj[key];
}
}
return clone;
}3. 深拷贝的实现方式
3.1 JSON.parse/stringify
这是最简单常用的深拷贝方法,但存在局限性。
const obj = {
name: 'zzy',
age: 18,
hobby: ['reading', 'coding']
};
const cloned = JSON.parse(JSON.stringify(obj));局限性:
- 不能拷贝
undefined、Symbol、函数等类型 - 不能拷贝
Date对象,会变成字符串 - 不能拷贝
RegExp对象,会变成空对象 - 不能拷贝
Error对象 - 不能拷贝循环引用的对象
- 不能拷贝
Map、Set等特殊对象
const obj = {
name: 'zzy',
date: new Date(),
regex: /test/,
fn: function() {},
sym: Symbol('test'),
und: undefined,
nil: null,
map: new Map([['a', 1]]),
set: new Set([1, 2, 3])
};
const cloned = JSON.parse(JSON.stringify(obj));
console.log(cloned.date); // "2026-04-16T15:13:35.000Z" (字符串)
console.log(cloned.regex); // {}
console.log(cloned.fn); // undefined
console.log(cloned.sym); // undefined
console.log(cloned.map); // {}
console.log(cloned.set); // {}3.2 递归拷贝
手写递归函数可以处理更复杂的情况,包括循环引用和特殊类型。
function deepClone(obj, hash = new WeakMap()) {
// 处理空值和基本类型
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理日期对象
if (obj instanceof Date) {
return new Date(obj);
}
// 处理正则对象
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
// 处理数组
if (Array.isArray(obj)) {
const cloneArr = [];
hash.set(obj, cloneArr);
obj.forEach((item, index) => {
cloneArr[index] = deepClone(item, hash);
});
return cloneArr;
}
// 处理普通对象
const cloneObj = {};
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}3.3 处理循环引用
循环引用是对象属性直接或间接引用自身的情况,上述 WeakMap 方案已经处理了这个问题。
const obj = {
name: 'zzy',
age: 18
};
obj.self = obj; // 循环引用
const cloned = deepClone(obj);
console.log(cloned.name); // 'zzy'
console.log(cloned.self === cloned); // true,循环引用被正确处理
console.log(cloned.self === obj); // false,完全独立的对象3.4 处理 Symbol 作为属性键
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (hash.has(obj)) return hash.get(obj);
const cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
// 复制普通属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
// 复制 Symbol 属性
const symbolKeys = Object.getOwnPropertySymbols(obj);
symbolKeys.forEach(sym => {
cloneObj[sym] = deepClone(obj[sym], hash);
});
return cloneObj;
}
const sym = Symbol('test');
const obj = {
name: 'zzy',
[sym]: 'symbol value'
};
const cloned = deepClone(obj);
console.log(cloned[sym]); // 'symbol value'
console.log(cloned[sym] === obj[sym]); // false,Symbol 被正确拷贝3.5 处理 Set 和 Map
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (hash.has(obj)) return hash.get(obj);
// 处理 Set
if (obj instanceof Set) {
const cloneSet = new Set();
hash.set(obj, cloneSet);
obj.forEach(value => {
cloneSet.add(deepClone(value, hash));
});
return cloneSet;
}
// 处理 Map
if (obj instanceof Map) {
const cloneMap = new Map();
hash.set(obj, cloneMap);
obj.forEach((value, key) => {
cloneMap.set(deepClone(key, hash), deepClone(value, hash));
});
return cloneMap;
}
const cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
const map = new Map([['a', 1], [{b: 2}, 3]]);
const set = new Set([1, 2, {c: 3}]);
const clonedMap = deepClone(map);
const clonedSet = deepClone(set);
console.log(clonedMap.get('a')); // 1
console.log(clonedSet.has(1)); // true
console.log(clonedMap.get({b: 2})); // undefined,key 是新对象3.6 完整的深拷贝函数
综合以上所有场景的完整实现:
function deepClone(obj, hash = new WeakMap()) {
// 处理空值和非对象类型
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理日期对象
if (obj instanceof Date) {
return new Date(obj);
}
// 处理正则对象
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
// 处理 Set
if (obj instanceof Set) {
const cloneSet = new Set();
hash.set(obj, cloneSet);
obj.forEach(value => {
cloneSet.add(deepClone(value, hash));
});
return cloneSet;
}
// 处理 Map
if (obj instanceof Map) {
const cloneMap = new Map();
hash.set(obj, cloneMap);
obj.forEach((value, key) => {
cloneMap.set(deepClone(key, hash), deepClone(value, hash));
});
return cloneMap;
}
// 处理数组
if (Array.isArray(obj)) {
const cloneArr = [];
hash.set(obj, cloneArr);
obj.forEach((item, index) => {
cloneArr[index] = deepClone(item, hash);
});
return cloneArr;
}
// 处理普通对象
const cloneObj = {};
hash.set(obj, cloneObj);
// 复制自有属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
// 复制 Symbol 属性
const symbolKeys = Object.getOwnPropertySymbols(obj);
symbolKeys.forEach(sym => {
cloneObj[sym] = deepClone(obj[sym], hash);
});
return cloneObj;
}4. 特殊类型的拷贝处理
4.1 函数拷贝
函数在深拷贝中通常是直接复制的,因为函数复用通常不是深拷贝的主要目的。
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (hash.has(obj)) return hash.get(obj);
// 函数拷贝
if (typeof obj === 'function') {
return obj; // 或使用 new Function('return ' + obj.toString())()
}
// ... 其他处理
}4.2 不可枚举属性和原型链属性
const parent = { p: 1 };
const obj = Object.create(parent); // obj 的原型是 parent
obj.a = 1;
obj.b = 2;
const cloned = deepClone(obj);
console.log(cloned.a); // 1
console.log(cloned.p); // undefined,原型属性未被拷贝
console.log(Object.getPrototypeOf(cloned) === parent); // false4.3 属性描述符
深拷贝默认不保留属性的描述符(如 writable、enumerable、configurable)。
const obj = {};
Object.defineProperty(obj, 'name', {
value: 'zzy',
enumerable: false,
writable: false,
configurable: false
});
const cloned = deepClone(obj);
console.log(Object.getOwnPropertyDescriptor(cloned, 'name'));
// { value: 'zzy', writable: true, enumerable: true, configurable: true }
// 默认描述符与原对象不同5. 性能对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JSON.parse/stringify | 简单、速度快 | 无法处理函数、Date、循环引用等 | 数据对象(不含特殊类型) |
| 递归拷贝 | 可处理所有类型 | 代码复杂、可能有栈溢出风险 | 复杂对象、需要保留特殊类型 |
| structuredClone | 原生 API、支持大多数类型 | 不支持函数、部分旧浏览器不兼容 | 现代浏览器环境 |
// structuredClone 是浏览器内置的深拷贝 API
const obj = { date: new Date(), map: new Map([['a', 1]]) };
const cloned = structuredClone(obj);
console.log(cloned.date instanceof Date); // true
console.log(cloned.map instanceof Map); // true6. 实际应用建议
6.1 选择合适的拷贝方式
// 简单数据对象,使用 JSON 方法
const data = { name: 'zzy', age: 18, hobby: ['reading'] };
const cloned = JSON.parse(JSON.stringify(data));
// 包含特殊类型,使用手写递归函数
const complex = { date: new Date(), fn: () => {}, sym: Symbol('test') };
const cloned = deepClone(complex);
// 现代浏览器,使用 structuredClone
const cloned = structuredClone(obj);6.2 注意事项
- 性能考虑:大对象深拷贝时注意栈溢出风险
- 内存占用:深拷贝会创建完整副本,注意内存使用
- 特殊对象:根据实际需求选择合适的拷贝方案
- 不可变数据:对于 Redux 等状态管理,推荐使用深拷贝防止意外修改
参考资料
