前端开发中,对象拷贝就像现实世界的复印机------但当你按下"复印"键时,有时得到的竟是会同步变化的"魔法纸张"。这背后的秘密,就是深浅拷贝的奇妙世界
一、初识拷贝:复印机还是传送门?
简单数据类型(数字、字符串等)的拷贝像真正的复印机:
ini
let a = 100;
let b = a; // 复印一份
b = 200; // 修改复印件
console.log(a); // 100 → 原文件纹丝不动
复杂数据类型(对象、数组)的拷贝却像传送门:
ini
let obj1 = { name: '张三' };
let obj2 = obj1; // 不是复印!是给同一对象贴新标签
obj2.name = '李四';
console.log(obj1.name); // '李四' → 原对象被修改了!
这就是引用传递的陷阱------我们操作的始终是同一个内存地址的数据。
二、浅拷贝:表面复印术
1. Object.assign():对象合并利器
ini
const target = { a: 1 };
const source = { b: 2 };
const result = Object.assign(target, source);
result.a = 11;
console.log(target.a); // 11 → 目标对象被修改!
关键特性:
-
后来者居上:同名属性会被覆盖
cssObject.assign({a:1}, {a:2}) // {a:2}
-
可拷贝Symbol类型
lessconst s = Symbol(); Object.assign({}, {[s]:123}) // {Symbol():123}
-
忽略
null
和undefined
javascriptObject.assign({}, null) // 安全跳过
2. 数组的浅拷贝三剑客
ini
// 方法1:扩展运算符
const newArr = [...arr];
// 方法2:slice()
const arr2 = arr.slice();
// 方法3:concat()
const arr3 = arr.concat();
致命缺陷:嵌套对象仍是传送门!
ini
const matrix = [[1,2], [3,4]];
const copy = matrix.slice();
copy[0][0] = 99;
console.log(matrix[0][0]); // 99 → 原数组被污染!
三、深拷贝:制造平行宇宙
1. JSON大法:简单粗暴
ini
const source = {
b: { name: 'xht' }
};
const newObj = JSON.parse(JSON.stringify(source));
newObj.b.name = '小红';
console.log(source.b.name); // 'xht' → 原对象安然无恙!
三大局限:
-
函数属性 → 消失!
-
undefined
/Symbol
→ 消失! -
循环引用 → 直接报错!
iniconst obj = { a:1 }; obj.self = obj; // 循环引用 JSON.stringify(obj); // 报错!
2. 手写深拷贝:解决JSON的遗憾
基础版递归实现:
bash
function clone(source) {
if (typeof source !== 'object') return source;
const cloneTarget = Array.isArray(source) ? [] : {};
for (const key in source) {
cloneTarget[key] = clone(source[key]); // 递归拷贝
}
return cloneTarget;
}
进阶问题:
-
循环引用导致栈溢出
iniconst obj = {a:1}; obj.self = obj; // 自引用 clone(obj); // 无限递归!
3. 终极方案:WeakMap破循环
arduino
function clone(target, map = new WeakMap()) {
if (typeof target !== 'object') return target;
// 检查是否已拷贝过
if (map.get(target)) return map.get(target);
const cloneTarget = Array.isArray(target) ? [] : {};
map.set(target, cloneTarget); // 存储映射关系
for (const key in target) {
cloneTarget[key] = clone(target[key], map); // 递归传递map
}
return cloneTarget;
}
为什么用WeakMap?
-
弱引用特性:不阻止垃圾回收
-
内存安全:避免内存泄漏
-
支持对象作为键名
iniconst obj = {a:1}; const map = new WeakMap(); map.set(obj, 'value');
四、实战中的拷贝艺术
1. 配置合并:Object.assign的舞台
arduino
function createUser(options) {
const defaults = { name: '匿名', age:18 };
// 用户参数覆盖默认值
const config = Object.assign({}, defaults, options);
console.log(config);
}
createUser({ name: '李四' }); // {name:'李四', age:18}
2. 深浅拷贝选择指南
场景 | 推荐方案 | 示例 |
---|---|---|
无嵌套对象 | Object.assign /扩展运算符 |
配置合并 |
简单嵌套结构 | JSON.parse(JSON.stringify) |
保存状态快照 |
含函数/Symbol | 手写深拷贝 | 复杂状态管理 |
循环引用 | WeakMap版深拷贝 | 树形数据结构 |
五、核心原理透析
- 内存模型 :
简单类型直接存储值,复杂类型存储内存地址* - 递归的本质 :
"剥洋葱式"逐层拷贝,每层创建新对象 - WeakMap魔法:

结语:选择你的武器
- 日常开发:优先考虑
Object.assign
和JSON方法
- 特殊需求:手写深拷贝解决复杂场景
- 终极武器:
lodash.cloneDeep
第三方库
记住这个黄金法则 :
当修改拷贝对象时,如果不想影响原对象------请用深拷贝!
就像在现实世界中,当你想真正复制一份契约而不是共享同一份时,你需要的是真正的复印机,而不是魔法传送门。深浅拷贝的选择,决定了你的代码世界是独立宇宙还是纠缠态的空间!