所属板块:1. 数据类型与内存机制
记录日期:2026-03-xx
更新:根据实际项目中拷贝需求补充
1. 拷贝的本质区别(复习栈/堆)
| 方式 | 拷贝内容 | 是否共享引用 | 修改拷贝后原对象是否变化 | 适用场景 |
|---|---|---|---|---|
| 直接赋值 | 复制栈中值/地址指针 | 是(引用类型共享) | 是 | 基本类型;临时引用传递 |
| 浅拷贝 | 只拷贝第一层(地址) | 是(嵌套对象共享) | 是 | 简单对象/数组,不嵌套太深 |
| 深拷贝 | 递归拷贝所有层 | 否 | 否 | 需要完全独立副本的场景 |
核心:引用类型直接赋值或浅拷贝,只复制了“指针”,改副本会影响原对象。
2. 常见浅拷贝方式对比
| 方法 | 优点 | 缺点 / 注意点 | 适用性 |
|---|---|---|---|
| Object.assign(target, source) | 原生、简单 | 只浅拷贝;target 会被修改;不拷贝 Symbol 属性 | 合并对象 |
| {...obj}(展开运算符) | 简洁、ES6+ | 只浅拷贝第一层;不拷贝 Symbol、非可枚举属性 | 快速克隆普通对象 |
| Array.prototype.slice() | 数组专用 | 只浅拷贝数组本身,不处理嵌套 | 一维数组 |
| Array.prototype.concat() | 数组合并 | 同上 | 数组合并 |
示例(浅拷贝的坑):
const obj = { a: 1, b: { c: 2 } };
const shallow = { ...obj };
shallow.a = 999; // 不影响原 obj
shallow.b.c = 999; // 原 obj.b.c 也变了 999
console.log(obj.b.c); // 999
3. 常见深拷贝方式(及其局限)
-
JSON.parse(JSON.stringify(obj))
最简单,但有严重缺陷:- 丢失函数、Symbol、undefined、正则、Date、Map/Set 等
- 无法处理循环引用(会报错)
- 性能一般
const obj = { a: 1, fn: () => {}, date: new Date(), reg: /abc/ }; const copy = JSON.parse(JSON.stringify(obj)); console.log(copy.fn); // undefined console.log(copy.date); // 变成字符串 -
结构化克隆 structuredClone(现代浏览器/Node 推荐)
2022年后浏览器支持,Node 17+ 支持- 支持 Date、RegExp、Map、Set、Error 等
- 支持循环引用(自动处理)
- 不支持函数、DOM节点、非结构化数据
const original = { a: { b: {} } }; original.a.b.self = original.a.b; // 循环引用 const clone = structuredClone(original); clone.a.b.self.c = 999; console.log(original.a.b.self.c); // undefined(不影响原对象)注意:目前(2026)兼容性已很好,但老项目/老浏览器仍需 polyfill 或 fallback。
4. 手写递归深拷贝(面试/项目常用方案)
目标:支持基本类型、普通对象、数组、循环引用(用 WeakMap 记录已拷贝对象)
function deepClone(obj, visited = new WeakMap()) {
// 基本类型 / null 直接返回
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理 Date、RegExp 等特殊内置对象
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 循环引用:已拷贝过,直接返回之前的结果
if (visited.has(obj)) {
return visited.get(obj);
}
// 创建新对象(数组或普通对象)
const clone = Array.isArray(obj) ? [] : {};
// 记录已拷贝(防止循环引用)
visited.set(obj, clone);
// 递归拷贝属性
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
clone[key] = deepClone(obj[key], visited);
}
}
return clone;
}
// 测试循环引用
const obj = { a: 1 };
obj.self = obj;
const copy = deepClone(obj);
copy.a = 999;
console.log(obj.a); // 仍为 1
console.log(copy.self === copy); // true(正确处理循环)
注意:
- 用 WeakMap 而不是 Map:WeakMap 的 key 是弱引用,对象回收时自动清理,不会造成额外内存泄漏
- 没处理 Symbol 属性(如需,可加 Object.getOwnPropertySymbols)
- 没处理函数(函数通常不需要深拷贝,或用其他方式)
5. 小结 & 实际使用建议
- 简单对象 → {...obj} 或 Object.assign
- 需要支持 Date/Map/Set/循环引用 → 优先 structuredClone(现代环境)
- 兼容性要求高 / 需自定义 → 手写 deepClone(用 WeakMap 防循环)
- 项目中建议封装一个 clone 函数,根据环境 fallback:
const clone = typeof structuredClone === 'function' ? structuredClone : deepClone;
第一板块(数据类型与内存机制)到此基本覆盖主要内容。
返回总目录:戳这里