在 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())
的局限性
- 无法处理
undefined
、function
、Symbol
(会被忽略或转为 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
方法处理特殊数据(如 Date
、BigInt
)。
希望这个对比能帮助你更好地理解深拷贝和浅拷贝!
拓展
如何使用 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) 特殊数据类型(如 Date
、RegExp
)
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()) |
❌ 报错 |
❌ 无法处理 Date 、RegExp 、BigInt |
较快 |
简单 JSON 数据 |
_.cloneDeep() |
✅ 正确处理 |
✅ 支持 Date 、RegExp 等 |
较慢(递归) |
复杂对象、循环引用 |
手动递归深拷贝 |
✅ 可自定义 |
✅ 灵活 |
取决于实现 |
需要完全控制时 |
6. 注意事项
- 性能问题:
_.cloneDeep()
是递归实现的,对于超大对象可能影响性能。
- 如果只需要浅拷贝,优先使用
Object.assign()
或展开运算符 ...
。
- 循环引用:
- Lodash 能正确处理循环引用,而
JSON.parse(JSON.stringify())
会报错。
- 特殊数据类型:
- Lodash 能正确复制
Date
、RegExp
、Map
、Set
等,而 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()
!