引言
看完这篇文章,就不要担心面试官问你拷贝的八股了
什么是深浅拷贝?
基本概念
- 浅拷贝:只复制对象的第一层属性,如果属性是引用类型,则复制的是引用地址
- 深拷贝:完全复制整个对象,包括所有嵌套的对象,新对象与原对象完全独立
为什么需要深浅拷贝?
// 原始对象
const original = {
name: 'John',
hobbies: ['reading', 'coding']
};
// 浅拷贝
const shallowCopy = {...original};
shallowCopy.hobbies.push('gaming');
console.log(original.hobbies); // ['reading', 'coding', 'gaming'] 被影响了!
// 深拷贝
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.hobbies.push('swimming');
console.log(original.hobbies); // ['reading', 'coding'] 不受影响
浅拷贝的实现方式
1. 扩展运算符
const obj = { a: 1, b: { c: 2 } };
const shallowCopy = { ...obj };
2. Object.assign()
const obj = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, obj);
3. 数组的浅拷贝方法
const arr = [1, 2, { a: 3 }];
const shallowCopy1 = arr.slice();
const shallowCopy2 = [...arr];
const shallowCopy3 = Array.from(arr);
手写浅拷贝函数
function shallowCopy(target) {
// 基本类型直接返回
if (typeof target !== 'object' || target === null) {
return target;
}
// 判断是数组还是对象
const copy = Array.isArray(target) ? [] : {};
// 遍历属性
for (let key in target) {
// 只复制自身属性(不复制原型链上的)
if (target.hasOwnProperty(key)) {
copy[key] = target[key];
}
}
return copy;
}
// 测试
const original = { a: 1, b: { c: 2 } };
const copy = shallowCopy(original);
console.log(copy); // { a: 1, b: { c: 2 } }
console.log(copy.b === original.b); // true 证明是浅拷贝
深拷贝的实现方式
1. JSON方法(有局限)
const obj = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj));
局限性:
- 不能处理函数、Symbol、undefined
- 不能处理循环引用
- 会丢失Date、RegExp等特殊对象的原型链
2. 第三方库(推荐)
// 使用lodash
const _ = require('lodash');
const deepCopy = _.cloneDeep(obj);
手写深拷贝函数
基础版(不考虑特殊类型)
function deepCopy(target) {
if (typeof target !== 'object' || target === null) {
return target;
}
const copy = Array.isArray(target) ? [] : {};
for (let key in target) {
if (target.hasOwnProperty(key)) {
copy[key] = deepCopy(target[key]);
}
}
return copy;
}
// 测试
const original = {
a: 1,
b: {
c: 2,
d: [3, 4]
}
};
const copy = deepCopy(original);
console.log(copy.b.d === original.b.d); // false 证明是深拷贝
进阶版(处理特殊类型和循环引用)
function deepCopy(target, map = new WeakMap()) {
// 基本类型直接返回
if (typeof target !== 'object' || target === null) {
return target;
}
// 处理循环引用
if (map.has(target)) {
return map.get(target);
}
// 处理特殊对象类型
const constructor = target.constructor;
if (/^(Date|RegExp|Map|Set)$/i.test(constructor.name)) {
return new constructor(target);
}
// 创建新对象并存入map
const copy = Array.isArray(target) ? [] : {};
map.set(target, copy);
// 复制Symbol属性
const symKeys = Object.getOwnPropertySymbols(target);
if (symKeys.length) {
symKeys.forEach(symKey => {
copy[symKey] = deepCopy(target[symKey], map);
});
}
// 复制普通属性
for (let key in target) {
if (target.hasOwnProperty(key)) {
copy[key] = deepCopy(target[key], map);
}
}
return copy;
}
// 测试
const obj = {
a: 1,
b: new Date(),
c: /abc/,
d: [1, 2, { e: 3 }],
[Symbol('f')]: 'symbol'
};
obj.self = obj; // 循环引用
const copy = deepCopy(obj);
console.log(copy);
console.log(copy.self === copy); // true 循环引用处理正确
console.log(copy.b instanceof Date); // true
console.log(copy.c instanceof RegExp); // true
深浅拷贝性能对比
// 测试数据
const data = {
a: 1,
b: {
c: 2,
d: [3, 4, { e: 5 }]
}
};
// 浅拷贝性能
console.time('shallow copy');
for (let i = 0; i < 10000; i++) {
shallowCopy(data);
}
console.timeEnd('shallow copy');
// 深拷贝性能
console.time('deep copy');
for (let i = 0; i < 10000; i++) {
deepCopy(data);
}
console.timeEnd('deep copy');
// JSON深拷贝性能
console.time('JSON deep copy');
for (let i = 0; i < 10000; i++) {
JSON.parse(JSON.stringify(data));
}
console.timeEnd('JSON deep copy');
应用场景
使用浅拷贝的场景
- 对象结构简单,没有嵌套引用
- 需要共享某些引用类型数据
- 性能要求高,数据量大的情况
使用深拷贝的场景
- 需要完全独立的新对象
- 对象结构复杂,有多层嵌套
- 需要修改嵌套对象而不影响原对象
总结
- 浅拷贝只复制第一层,深拷贝复制所有层级
- 手写浅拷贝相对简单,深拷贝需要考虑多种边界情况
- JSON方法实现深拷贝有局限性,特殊类型和循环引用会出问题
- 实际开发中,复杂场景推荐使用成熟的工具库如lodash的cloneDeep
- 根据实际需求选择拷贝方式,避免不必要的性能开销