浅说深拷贝(Deep Copy)与浅拷贝(Shallow Copy)

在 JavaScript 中,深拷贝浅拷贝是两种不同的数据复制方式,主要区别在于如何处理引用类型数据(如对象、数组)。

1. 浅拷贝(Shallow Copy)

  • 定义:只复制对象的第一层属性,如果属性是引用类型(如对象、数组),则复制的是引用,而不是实际数据。
  • 特点
    • 原始对象和拷贝后的对象共享引用类型的属性
    • 修改其中一个对象的引用类型属性会影响另一个对象。
  • 实现方式
    • Object.assign()(仅第一层深拷贝,其余浅拷贝)
    • 展开运算符 ...(同上)
    • Array.prototype.slice()(数组浅拷贝)
    • Array.prototype.concat()(数组浅拷贝)
示例(浅拷贝)
const original = {
a: 1,
b: { c: 2 },
d: [3, 4],
};
// 浅拷贝方式1:Object.assign
const shallowCopy1 = Object.assign({}, original);
// 浅拷贝方式2:展开运算符
const shallowCopy2 = { ...original };
// 修改原始对象的引用类型属性
original.b.c = 99;
original.d.push(100);
console.log(shallowCopy1.b.c); // 99(受影响)
console.log(shallowCopy2.d); // [3, 4, 100](受影响)

结论:浅拷贝后,original 和 shallowCopy 共享引用类型的属性,修改会互相影响。

2. 深拷贝(Deep Copy)

  • 定义:递归复制对象的所有层级,包括嵌套的对象和数组,生成一个完全独立的新对象。
  • 特点
    • 原始对象和拷贝后的对象完全独立,修改不会互相影响。
  • 实现方式
    • JSON.parse(JSON.stringify(obj))(简单但有局限性)
    • 手动递归复制(灵活但代码较多)
    • 第三方库(如 Lodash 的 _.cloneDeep()
示例(深拷贝)
const original = {
a: 1,
b: { c: 2 },
d: [3, 4],
};
// 深拷贝方式1:JSON 方法(简单但有限制)
const deepCopy1 = JSON.parse(JSON.stringify(original));
// 深拷贝方式2:Lodash 的 _.cloneDeep(推荐)
// const _ = require('lodash');
// const deepCopy2 = _.cloneDeep(original);
// 修改原始对象的引用类型属性
original.b.c = 99;
original.d.push(100);
console.log(deepCopy1.b.c); // 2(不受影响)
console.log(deepCopy1.d); // [3, 4](不受影响)

结论:深拷贝后,original 和 deepCopy 完全独立,修改不会互相影响。

3. 浅拷贝 vs 深拷贝对比

特性 浅拷贝 深拷贝
复制层级 仅第一层 递归复制所有层级
引用类型处理 复制引用(共享数据) 复制实际数据(独立)
修改影响 原始对象和拷贝对象互相影响 完全独立,互不影响
实现方式 Object.assign()... JSON.parse(JSON.stringify())_.cloneDeep()
性能 较快(仅复制第一层) 较慢(递归复制所有数据)

4. 注意事项

(1) JSON.parse(JSON.stringify()) 的局限性
  • 无法处理 undefinedfunctionSymbol(会被忽略或转为 null)。
  • 循环引用会报错(如 const obj = {}; obj.self = obj;)。
  • Date 对象会被转为字符串(失去 Date 类型)。
  • BigInt 会报错(无法序列化)。
(2) 手动实现深拷贝(递归)
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj; // 原始值直接返回
}
if (obj instanceof Date) {
return new Date(obj); // 处理 Date
}
if (obj instanceof Array) {
return obj.map(item => deepClone(item)); // 数组递归复制
}
if (obj instanceof Object) {
const clonedObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key]); // 对象递归复制
}
}
return clonedObj;
}
return obj; // 其他情况(如函数)直接返回
}
(3) 推荐方案
  • 简单场景JSON.parse(JSON.stringify())(但注意局限性)。
  • 复杂场景:使用 Lodash 的 _.cloneDeep()(稳定可靠)。
  • 循环引用:使用 flatted 或 circular-json 等库。

总结

  • 浅拷贝:适合简单对象,仅复制第一层,引用类型共享。
  • 深拷贝:适合复杂对象,递归复制所有层级,完全独立。
  • 选择方式
    • 简单数据 → 浅拷贝(Object.assign() 或 ...)。
    • 复杂数据 → 深拷贝(_.cloneDeep() 或手动递归)。
    • 避免 JSON 方法处理特殊数据(如 DateBigInt)。

希望这个对比能帮助你更好地理解深拷贝和浅拷贝!

拓展

如何使用 Lodash 的 _.cloneDeep() 进行深拷贝

_.cloneDeep() 是 Lodash 库提供的一个实用函数,用于递归复制一个对象或数组,生成一个完全独立的深拷贝。以下是详细使用方法:

1. 安装 Lodash

如果尚未安装 Lodash,可以通过 npm 或 yarn 安装:

npm install lodash
# 或
yarn add lodash

2. 引入 _.cloneDeep()

方式 1:全局引入(不推荐,适用于简单项目)
import _ from 'lodash'; // 引入整个 Lodash 库
const original = { a: 1, b: { c: 2 }, d: [3, 4] };
const deepCopy = _.cloneDeep(original);
方式 2:按需引入(推荐,减少打包体积)
import cloneDeep from 'lodash/cloneDeep'; // 仅引入 cloneDeep 方法
const original = { a: 1, b: { c: 2 }, d: [3, 4] };
const deepCopy = cloneDeep(original); // 直接使用

3. 基本用法

import cloneDeep from 'lodash/cloneDeep';
const original = {
name: 'Alice',
age: 25,
address: {
city: 'New York',
zip: 10001,
},
hobbies: ['reading', 'coding'],
};
// 深拷贝
const deepCopy = cloneDeep(original);
// 修改原始对象
original.address.city = 'Los Angeles';
original.hobbies.push('gaming');
// 深拷贝的对象不受影响
console.log(deepCopy.address.city); // "New York"
console.log(deepCopy.hobbies); // ["reading", "coding"]

4. 适用场景

(1) 复杂对象(嵌套对象/数组)
const complexObj = {
a: 1,
b: {
c: [2, 3, { d: 4 }],
},
};
const copy = cloneDeep(complexObj);
complexObj.b.c[2].d = 99; // 修改原始对象
console.log(copy.b.c[2].d); // 4(深拷贝不受影响)
(2) 循环引用(Lodash 能正确处理)
const obj = {};
obj.self = obj; // 循环引用
const copy = cloneDeep(obj);
console.log(copy.self === copy); // true(深拷贝保留循环引用结构)
(3) 特殊数据类型(如 DateRegExp
const data = {
date: new Date(),
regex: /test/g,
};
const copy = cloneDeep(data);
console.log(copy.date instanceof Date); // true
console.log(copy.regex instanceof RegExp); // true

5. 对比其他深拷贝方式

方法 能否处理循环引用 能否处理特殊类型 性能 适用场景
JSON.parse(JSON.stringify()) ❌ 报错 ❌ 无法处理 DateRegExpBigInt 较快 简单 JSON 数据
_.cloneDeep() ✅ 正确处理 ✅ 支持 DateRegExp 等 较慢(递归) 复杂对象、循环引用
手动递归深拷贝 ✅ 可自定义 ✅ 灵活 取决于实现 需要完全控制时

6. 注意事项

  1. 性能问题
    • _.cloneDeep() 是递归实现的,对于超大对象可能影响性能。
    • 如果只需要浅拷贝,优先使用 Object.assign() 或展开运算符 ...
  2. 循环引用
    • Lodash 能正确处理循环引用,而 JSON.parse(JSON.stringify()) 会报错。
  3. 特殊数据类型
    • Lodash 能正确复制 DateRegExpMapSet 等,而 JSON 方法会丢失类型信息。

7. 替代方案(如果不想用 Lodash)

(1) 使用结构化克隆(现代浏览器支持)
// 适用于浏览器环境(如 Chrome 79+)
const original = { a: 1, b: { c: 2 } };
const clone = structuredClone(original); // 类似深拷贝
(2) 使用第三方库
  • flatted(处理循环引用)
  • rfdc(高性能深拷贝)

总结

  • _.cloneDeep() 是最可靠的深拷贝方式,适用于复杂对象、循环引用和特殊数据类型。
  • 按需引入import cloneDeep from 'lodash/cloneDeep')可以减少打包体积。
  • 如果项目运行在现代浏览器,也可以考虑 structuredClone()

希望这个指南能帮助你正确使用 _.cloneDeep()

你可能感兴趣的:(javascript,前端,开发语言)