目录
1. JSON.parse(JSON.stringify())
3. 第三方库(如 Lodash 的 _.cloneDeep())
浅拷贝(Shallow Copy)
一、浅拷贝的定义
-
核心概念:创建一个新对象,仅复制原始对象的 第一层属性值。若属性为 基本类型,拷贝其值;若为 引用类型(如对象、数组),则拷贝其内存地址(共享同一引用)。
-
特点:
-
修改新对象的 引用类型属性 会影响原对象。
-
修改新对象的 基本类型属性 不会影响原对象。
-
二、直接赋值 vs 浅拷贝
1. 直接赋值
-
本质:将变量指向原对象的 内存地址,未创建新对象。
-
结果:新旧变量完全共享同一对象,任意修改均相互影响。
const obj = { a: 1, b: { c: 2 } }; const copy = obj; // 直接赋值 copy.a = 10; copy.b.c = 20; console.log(obj.a); // 10(基本类型也被修改) console.log(obj.b.c); // 20
2. 浅拷贝
-
本质:创建新对象,复制原对象的 第一层属性值。
-
结果:
-
基本类型属性:独立修改,互不影响。
-
引用类型属性:共享同一内存地址,修改相互影响。
const obj = { a: 1, b: { c: 2 } }; const copy = { ...obj }; // 浅拷贝(扩展运算符) copy.a = 10; // 修改基本类型属性 copy.b.c = 20; // 修改引用类型属性 console.log(obj.a); // 1(原对象未变) console.log(obj.b.c); // 20(原对象被修改)
-
三、数组的浅拷贝方法
1. slice()
const arr = [1, 2, { a: 3 }];
const copy = arr.slice(); // 浅拷贝数组
copy[0] = 10; // 修改基本类型元素
copy[2].a = 30; // 修改引用类型元素
console.log(arr[0]); // 1(原数组未变)
console.log(arr[2].a); // 30(原数组被修改)
2. concat()
const arr = [1, 2, { a: 3 }];
const copy = arr.concat(); // 浅拷贝数组
3. 扩展运算符(...
)
const arr = [1, 2, { a: 3 }];
const copy = [...arr]; // 浅拷贝数组
四、对象的浅拷贝方法
1. Object.assign()
const obj = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, obj); // 浅拷贝对象
copy.a = 10; // 修改基本类型属性
copy.b.c = 20; // 修改引用类型属性
console.log(obj.a); // 1(原对象未变)
console.log(obj.b.c); // 20(原对象被修改)
2. 扩展运算符(...
)
const obj = { a: 1, b: { c: 2 } };
const copy = { ...obj }; // 浅拷贝对象
五、浅拷贝的局限性
-
嵌套引用类型:浅拷贝无法处理多层级对象或数组中的引用类型属性。
const original = { a: 1, b: { c: 2 }, d: [3] }; const copy = { ...original }; copy.b.c = 20; // 原对象被修改 copy.d.push(4); // 原数组被修改 console.log(original.b.c); // 20 console.log(original.d); // [3, 4]
六、总结
-
浅拷贝:复制对象的第一层属性,适用于简单数据结构。
-
直接赋值:共享内存地址,无独立副本。
-
数组浅拷贝:
slice()
、concat()
、扩展运算符。 -
对象浅拷贝:
Object.assign()
、扩展运算符。 -
深层拷贝需求:需使用深拷贝方法。
深拷贝(Deep Copy)
一、深拷贝的定义
-
核心概念:创建一个新对象,递归复制原始对象的所有层级属性(包括嵌套对象和数组),新对象与原对象完全独立,修改互不影响。
-
特点:
-
完全独立内存:所有层级的引用类型属性均为新对象。
-
适用场景:复杂嵌套对象、需完全隔离数据的场景(如状态管理、数据快照)。
-
二、深拷贝的常见实现方式
1. JSON.parse(JSON.stringify())
-
原理:将对象序列化为 JSON 字符串,再解析为新的对象。
-
示例:
const obj = { a: 1, b: { c: 2 }, d: [3] }; const copy = JSON.parse(JSON.stringify(obj)); copy.b.c = 20; console.log(obj.b.c); // 2(原对象未变)
-
优点:简单快捷,适用于大多数 JSON 安全的数据。
-
缺点:
-
不支持特殊类型:忽略
undefined
、Symbol
、函数、循环引用。 -
破坏原型链:无法复制对象的构造函数和原型方法。
-
日期对象:会被转换为 ISO 字符串,反序列化为字符串而非 Date 对象。
-
2. 递归手动实现
-
原理:递归遍历对象属性,逐层复制基本类型和引用类型。
-
示例:
简易版:
function deepCopy(newObj, oldObj) {
debugger //类似于在代码中设置断点
for (let k in oldObj) {
// 处理数组的问题 一定先写数组再写对象 不能颠倒
if (oldObj[k] instanceof Array) {
newObj[k] = []
deepCopy(newObj[k], oldObj[k])
}
else if (oldObj[k] instanceof Object) {
newObj[k] = {}
deepCopy(newObj[k], oldObj[k])
}
else {
newObj[k] = oldObj[k]
}
}
}
升级版:
function deepClone(source, map = new WeakMap()) {
// 处理基本类型和 null
if (source === null || typeof source !== 'object') return source;
// 处理循环引用
if (map.has(source)) return map.get(source);
// 处理数组和对象
const target = Array.isArray(source) ? [] : {};
map.set(source, target);
// 递归复制属性
for (const key in source) {
if (source.hasOwnProperty(key)) {
target[key] = deepClone(source[key], map);
}
}
return target;
}
// 使用示例
const obj = { a: 1, b: { c: 2 }, d: [3] };
const copy = deepClone(obj);
copy.b.c = 20;
console.log(obj.b.c); // 2(原对象未变)
-
优点:
-
支持所有数据类型(需扩展代码处理
Date
、RegExp
、Map
、Set
等)。 -
可处理循环引用(通过 WeakMap 缓存)。
-
-
缺点:
-
实现复杂,需处理多种边界情况。
-
性能较低(递归遍历耗时)。
-
3. 第三方库(如 Lodash 的 _.cloneDeep()
)
-
原理:成熟的深拷贝实现,处理了各种数据类型和边界条件。
-
示例:
const _ = require('lodash'); const obj = { a: 1, b: { c: 2 }, d: [3] }; const copy = _.cloneDeep(obj); copy.b.c = 20; console.log(obj.b.c); // 2
-
优点:
-
功能全面,支持复杂类型(如
Date
、RegExp
、Map
、Set
)。 -
高性能且经过严格测试。
-
-
缺点:需引入外部库。
4. MessageChannel
(异步深拷贝)
-
原理:利用浏览器 API 的通信机制实现深拷贝。
-
示例:
function deepClone(obj) { return new Promise(resolve => { const { port1, port2 } = new MessageChannel(); port2.onmessage = ev => resolve(ev.data); port1.postMessage(obj); }); } // 使用示例(异步) const obj = { a: 1, b: { c: 2 } }; deepClone(obj).then(copy => { copy.b.c = 20; console.log(obj.b.c); // 2 });
-
优点:可处理循环引用和部分特殊对象(如
Date
)。 -
缺点:
-
异步操作,使用不便。
-
无法复制函数、
undefined
等非序列化数据。 -
仅限浏览器环境。
-
三、深拷贝的边界条件处理
1. 循环引用
-
问题:对象属性间接或直接引用自身,导致递归无限循环。
-
解决:使用
WeakMap
缓存已拷贝对象。const obj = { a: 1 }; obj.self = obj; // 循环引用 const copy = deepClone(obj); // 正确处理
2. 特殊对象类型
-
处理方式:
-
Date
:创建新Date
实例。 -
RegExp
:复制正则表达式和标志。 -
Map
/Set
:递归复制其内容。
// 扩展递归函数处理 Date if (source instanceof Date) return new Date(source);
-
四、总结
方法 | 推荐场景 | 注意事项 |
---|---|---|
JSON 方法 | 简单 JSON 安全数据(无特殊类型) | 忽略函数、undefined 、循环引用 |
递归手动实现 | 需要完全控制深拷贝逻辑 | 需扩展处理特殊类型和循环引用 |
Lodash.cloneDeep | 生产环境,复杂数据类型 | 引入外部依赖 |
MessageChannel | 浏览器环境,支持部分特殊对象 | 异步操作,无法复制函数 |
最佳实践:
-
优先使用 Lodash 等成熟库:减少重复造轮子和潜在错误。
-
手动实现需谨慎:充分测试循环引用、特殊数据类型和性能。
-
避免滥用深拷贝:在数据隔离需求明确时使用,避免不必要的性能损耗。
五、浅拷贝 vs 深拷贝对比
特性 | 浅拷贝 | 深拷贝 |
---|---|---|
复制层级 | 仅复制第一层属性 | 递归复制所有层级属性 |
内存占用 | 低(共享引用类型属性) | 高(完全独立副本) |
性能 | 高(仅复制第一层) | 较低(递归遍历耗时) |
适用场景 | 简单对象或无需隔离引用属性的场景 | 需要完全隔离数据的场景 |
实现复杂度 | 低(简单复制) | 高(需处理循环引用、特殊类型) |
六、_.cloneDeep(value)
源码解析
1. 核心逻辑
_.cloneDeep
用于深拷贝任意类型的数据,包括对象、数组、Date
、RegExp
、Map
、Set
等。其核心实现依赖于递归遍历和类型判断。
2. 关键源码步骤
function cloneDeep(value) {
return baseClone(value, true /* deep */);
}
// 基础克隆函数(Lodash 内部实现)
function baseClone(value, isDeep, customizer, key, object, stack) {
// 使用栈跟踪循环引用
stack || (stack = new Stack());
// 处理已拷贝的对象(避免循环引用)
if (stack.has(value)) {
return stack.get(value);
}
let result;
// 根据类型选择克隆策略
if (isObject(value)) {
// 处理数组、对象、Map、Set、Date、RegExp 等
const tag = getTag(value);
switch (tag) {
case '[object Array]':
result = initCloneArray(value);
break;
case '[object Date]':
result = new Date(value.getTime());
break;
case '[object Map]':
result = new Map();
value.forEach((subValue, key) => {
result.set(key, baseClone(subValue, isDeep, customizer, key, value, stack));
});
break;
// 其他类型处理(如 Set、RegExp 等)
// ...
}
// 注册到栈中,处理循环引用
stack.set(value, result);
// 递归拷贝属性
copyProperties(value, result, isDeep, customizer, stack);
} else {
// 处理基本类型(直接返回)
result = value;
}
return result;
}
3. 核心特性
-
循环引用处理:使用
Stack
结构(内部基于数组或WeakMap
)记录已拷贝对象。 -
类型识别:通过
Object.prototype.toString.call(value)
获取精确类型。 -
递归拷贝:对嵌套结构逐层复制,确保深层次独立性。