大家好,我是鱼樱!!!
关注公众号【鱼樱AI实验室】
持续每天分享更多前端和AI辅助前端编码新知识~~喜欢的就一起学反正开源至上,无所谓被诋毁被喷被质疑文章没有价值~~~坚持自己观点
写点笔记写点生活~写点经验。
在当前环境下,纯前端开发者可以通过技术深化、横向扩展、切入新兴领域以及产品化思维找到突破口。
为什么需要克隆?
JavaScript 中直接赋值对象或数组时,传递的是引用地址而非独立副本。修改克隆对象会影响原对象,导致难以追踪的 Bug。掌握正确的克隆姿势是前端开发的必备技能!
一、浅拷贝:仅复制第一层属性
适用场景:简单对象、无嵌套结构、无需修改子属性。
1. Object.assign()
javascript
const objClone = Object.assign({}, originalObj);
-
原理:合并对象属性到新对象。
-
缺点:嵌套对象仍是引用。
-
示例 :
javascriptobjClone.name = '新名字'; // 不影响原对象 objClone.friends.name = '改'; // 原对象的 friends 也会被修改!
2. 展开运算符 ...
javascript
const objClone = { ...originalObj };
const arrClone = [ ...originalArr ];
- 特点 :语法简洁,与
Object.assign()
等效。 - 注意:同样无法处理嵌套结构。
3. 数组的 slice()
javascript
const arrClone = originalArr.slice();
- 本质:截取数组全部元素生成新数组。
- 局限:仅适用于数组,且不处理嵌套对象。
二、深拷贝:完全独立副本,断绝所有引用
适用场景:复杂对象、嵌套结构、需要完全隔离修改。
4. JSON.parse(JSON.stringify())
javascript
const deepClone = JSON.parse(JSON.stringify(originalObj));
- 原理:通过 JSON 序列化反序列化生成新对象。
- 致命缺陷 :
- 丢失
undefined
、Symbol
、函数属性。 - 无法处理循环引用(如
obj.self = obj
)。
- 丢失
- 适用场景:纯数据对象(无特殊类型)。
5. structuredClone()
javascript
const deepClone = structuredClone(originalObj);
- 特点 :
- 浏览器原生 API,支持
undefined
、Date
、RegExp
等。 - 可处理循环引用(部分浏览器支持)。
- 浏览器原生 API,支持
- 注意 :兼容性问题(IE 不支持),检查 Can I Use。
6. 手动递归实现
javascript
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (hash.has(obj)) return hash.get(obj); // 解决循环引用
const clone = Array.isArray(obj) ? [] : {};
hash.set(obj, clone);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], hash);
}
}
return clone;
}
- 优势 :完全可控,可扩展处理特殊类型(如
Date
、Map
)。 - 优化点 :
- 使用
WeakMap
解决循环引用。 - 增加类型判断处理边界情况。
- 使用
三、方案对比与选型指南
方法 | 深浅拷贝 | 处理循环引用 | 保留特殊类型 | 性能 |
---|---|---|---|---|
Object.assign |
浅 | ❌ | ❌ | 高 |
JSON 序列化 |
深 | ❌ | ❌ | 中 |
structuredClone |
深 | ✔️(部分) | ✔️ | 高 |
手动递归 | 深 | ✔️ | ✔️(需扩展) | 低(复杂对象) |
四、实际开发中的坑与解决方案
-
循环引用报错
- 使用
WeakMap
缓存已拷贝对象(参考手动实现代码)。
- 使用
-
函数属性丢失
-
手动实现中增加函数处理逻辑:
javascriptif (typeof obj === 'function') return obj.bind({});
-
-
特殊对象处理(如
Date
)-
在递归中识别类型并重建:
javascriptif (obj instanceof Date) return new Date(obj);
-
附上实践案例
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>对象克隆</title>
</head>
<body>
<script>
// 定义一个对象
const obj1 = {
name: 'obj1',
age: 18,
sex: '男',
friends: {
name: 'obj2',
age: 18,
sex: '男'
},
address: undefined // 包含 undefined 的属性
}
// 浅拷贝
const obj3 = Object.assign({}, obj1);
obj3.name = 'obj3';
obj3.friends.name = 'obj4';
console.log(obj1); // {name: 'obj1', age: 18, sex: '男', friends: {...}, address: undefined}
console.log(obj3); // {name: 'obj1', age: 18, sex: '男', friends: {...}, address: undefined}
console.log(obj1 === obj3); // false
console.log(obj1.friends === obj3.friends); // true
console.log(obj1.friends.name === obj3.friends.name); // true
// 浅拷贝二 如果是数组可以使用 slice() 方法
const obj4 = {...obj1};
obj4.name = 'obj4';
obj4.friends.name = 'obj5';
console.log(obj1); // {name: 'obj1', age: 18, sex: '男', friends: {...}, address: undefined}
console.log(obj4); // {name: 'obj1', age: 18, sex: '男', friends: {...}, address: undefined}
console.log(obj1 === obj4); // false
console.log(obj1.friends === obj4.friends); // true
console.log(obj1.friends.name === obj4.friends.name); // true
// 克隆对象 - 深度copy Lodash 的 _.cloneDeep() 方法
// 但从性能和某些特殊对象的处理(例如包含函数、undefined、Symbol 或循环引用的对象 则不适用)角度来看,这种方法并不是最优的深拷贝实现方式
const obj2 = JSON.parse(JSON.stringify(obj1));
// 修改克隆对象
obj2.name = 'obj2';
obj2.friends.name = 'obj3';
// 打印对象
console.log(obj1); // {name: 'obj1', age: 18, sex: '男', friends: {...}, address: undefined}
console.log(obj2); // {name: "obj2", age: 18, sex: "男", friends: {name: "obj3", age: 18, sex: "男"}}
console.log(obj1 === obj2); // false
console.log(obj1.friends === obj2.friends); // false
console.log(obj1.friends.name === obj2.friends.name); // false
// 某些现代浏览器支持使用 structuredClone 方法进行深拷贝
const obj5 = structuredClone(obj1);
obj5.name = 'obj5';
obj5.friends.name = 'obj6';
console.log(obj1); // {name: 'obj1', age: 18, sex: '男', friends: {...}, address: undefined}
console.log(obj5); // {name: 'obj1', age: 18, sex: '男', friends: {...}, address: undefined}
console.log(obj1 === obj5); // false
console.log(obj1.friends === obj5.friends); // false
console.log(obj1.friends.name === obj5.friends.name); // false
// 手动实现是否深度拷贝 默认是浅拷贝
function clone(obj, deep = false) {
// 如果是基本类型或者 null 直接返回
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// 如果是数组
if (Array.isArray(obj)) {
return deep ? obj.map(item => clone(item, deep)) : obj.slice();
}
// 如果是对象
const result = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deep ? clone(obj[key], deep) : obj[key];
}
}
return result;
}
// 浅拷贝
const obj6 = clone(obj1);
obj6.name = 'obj6';
obj6.friends.name = 'obj7';
console.log(obj1); // {name: 'obj1', age: 18, sex: '男', friends: {...}, address: undefined}
console.log(obj6,'手动实现浅拷贝'); // {name: 'obj1', age: 18, sex: '男', friends: {...}, address: undefined}
console.log(obj1 === obj6); // false
console.log(obj1.friends === obj6.friends); // true
console.log(obj1.friends.name === obj6.friends.name); // true
// 深拷贝
const obj7 = clone(obj1, true);
obj7.name = 'obj7';
obj7.friends.name = 'obj8';
console.log(obj1); // {name: 'obj1', age: 18, sex: '男', friends: {...}, address: undefined}
console.log(obj7,'手动实现深拷贝'); // {name: 'obj7', age: 18, sex: '男', friends: {name: 'obj8', age: 18, sex: '男'}, address: undefined}
console.log(obj1 === obj7); // false
console.log(obj1.friends === obj7.friends); // false
console.log(obj1.friends.name === obj7.friends.name); // false
</script>
</body>
</html>
五、总结
- 简单场景 :优先使用
...
或Object.assign()
。 - 数据克隆 :
JSON
方法快捷但需注意类型丢失。 - 生产环境 :使用
structuredClone
或成熟工具库(如 Lodash 的_.cloneDeep()
)。 - 极致控制:手动实现深拷贝,按需扩展。
记住:没有完美的深拷贝方案,只有最适合当前场景的选择!