一、拷贝的本质与分类
拷贝的本质是基于原对象创建新对象。根据对嵌套结构的处理方式分为两类:
- 浅拷贝:仅复制对象的第一层属性,子对象仍共享内存地址
- 深拷贝:递归复制所有层级属性,新旧对象完全独立
二、浅拷贝的实现与原理
1. 常用方法
javascript
// 对象浅拷贝
const obj = { a: 1, b: { n: 2 } };
const objCopy1 = Object.assign({}, obj);
const objCopy2 = { ...obj };
// 数组浅拷贝
const arr = [1, 2, { n: 3 }];
const arrCopy1 = arr.slice();
const arrCopy2 = [...arr];
2. 原型链拷贝
javascript
const obj = { a: 1 };
const newObj = Object.create(obj);
obj.a = 2;
console.log(newObj.a); // 输出2(共享原型属性)
3. 浅拷贝的局限性
javascript
const original = {
a: 1,
b: {
inner: { n: 2 }
}
};
const copy = Object.assign({}, original);
original.b.inner.n = 100;
console.log(copy.b.inner.n); // 输出100(子对象被修改)
三、深拷贝的实现方案
1. JSON 序列化法
javascript
const obj = {
name: 'John',
like: { game: 'Chess' },
date: new Date(),
func: () => console.log('test'),
sym: Symbol('key'),
infinity: Infinity
};
const deepCopy = JSON.parse(JSON.stringify(obj));
console.log(deepCopy);
// 输出:{ name: 'John', like: { game: 'Chess' }, date: "2024-05-15T..." }
// 丢失:函数、Symbol、Infinity 等特殊类型
缺陷:
- 无法处理:
undefined
、Symbol
、Function
- 转换异常:
NaN
→null
,Date
→ 字符串 - 循环引用报错:
obj.self = obj
2. structuredClone API
javascript
const user = {
name: 'Alice',
like: { game: 'Piano' },
tags: new Set(['music', 'art'])
};
const clone = structuredClone(user);
user.like.game = 'Guitar';
console.log(clone.like.game); // 保持'Piano'(深拷贝)
console.log(clone.tags instanceof Set); // true(保留特殊对象)
优势 :
支持循环引用
保留对象类型(Date、Set、Map等)
处理特殊值(NaN
、Infinity
)
限制 :
无法克隆函数、DOM节点
旧浏览器需polyfill
3. 手动实现深拷贝
javascript
function deepClone(obj, cache = new WeakMap()) {
// 处理循环引用
if(cache.has(obj)) return cache.get(obj);
// 处理原始类型
if(typeof obj !== 'object' || obj === null) return obj;
// 处理特殊对象
const Constructor = obj.constructor;
if(/^(Date|RegExp|Map|Set)$/i.test(Constructor.name)) {
return new Constructor(obj);
}
// 创建新对象
const clone = Array.isArray(obj) ? [] : {};
cache.set(obj, clone);
// 递归拷贝
Reflect.ownKeys(obj).forEach(key => {
clone[key] = deepClone(obj[key], cache);
});
return clone;
}
// 测试用例
const obj = { a: new Date(), b: [1, {n:2}] };
obj.self = obj;
const copy = deepClone(obj);
四、拷贝技术对比
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Object.assign |
简单快速 | 仅浅拷贝 | 简单对象拷贝 |
JSON 方法 |
兼容性好 | 丢失特殊类型 | 无特殊类型的纯数据对象 |
structuredClone |
支持复杂类型 | 浏览器兼容性问题 | 现代浏览器环境 |
手动实现 | 完全可控 | 实现成本高 | 需要处理特殊类型或定制化场景 |
五、高级场景处理
1. 原型链属性拷贝
javascript
function cloneWithProto(obj) {
const proto = Object.getPrototypeOf(obj);
const copy = Object.create(proto);
Object.entries(obj).forEach(([key, val]) => {
copy[key] = deepClone(val);
});
return copy;
}
2. 处理不可枚举属性
javascript
function cloneAllProperties(obj) {
const props = Object.getOwnPropertyDescriptors(obj);
const clone = Object.create(
Object.getPrototypeOf(obj),
Object.entries(props).reduce((acc, [key, desc]) => {
desc.value = deepClone(desc.value);
acc[key] = desc;
return acc;
}, {})
);
return clone;
}
六、性能优化建议
- 对象冻结 :对不需要修改的对象使用
Object.freeze()
- 拷贝缓存:使用WeakMap记录已拷贝对象
- 按需拷贝:仅拷贝实际用到的属性
- 类型检测:优先处理已知类型提升性能
总结
选择拷贝方案时需要综合考量:
- 数据类型复杂度:是否包含特殊对象
- 性能要求:大数据量时避免深度递归
- 运行环境:浏览器兼容性要求
- 功能需求:是否需要保留原型链等特性
掌握不同拷贝技术的原理与适用场景,能够有效提升代码质量与执行效率。