10. 怎么实现深拷贝?
总结
实现深拷贝的方式有多种,选择应根据具体需求:
- 简单场景:使用
JSON.parse(JSON.stringify(obj))
- 学习/教学:使用递归实现 + 循环引用处理
- 生产环境:优先使用
lodash.cloneDeep
- 浏览器端(现代):使用
structuredClone
✅ 建议:在开发中优先使用成熟的库(如
lodash
),避免重复造轮子。如需自定义实现,应考虑特殊类型、循环引用、性能等问题。
概述
在 JavaScript 中,深拷贝是指创建一个新对象,使其与原对象完全独立,互不影响。与之相对的是浅拷贝,只复制引用地址,原对象和新对象共享内部引用的数据。
深拷贝常用于:
- 状态快照保存(如撤销/重做功能)
- 数据隔离(避免修改原始数据)
- 跨组件通信时的数据传递
一、常见的深拷贝方式
1. 使用 JSON.parse(JSON.stringify(obj))
(简单但有局限)
原理:
将对象序列化为 JSON 字符串,再解析为新对象。
示例:
const obj = { a: 1, b: { c: 2 } };
const copy = JSON.parse(JSON.stringify(obj));copy.b.c = 3;
console.log(obj.b.c); // 2(原对象未被修改)
优点:
- 简洁、无需额外代码
- 支持大多数基础结构
缺点:
限制 | 说明 |
---|---|
不支持函数、undefined | 会被忽略 |
不支持循环引用 | 会报错 |
不支持 Symbol 属性键 | 会被忽略 |
日期对象会被转为字符串 | 无法还原为 Date 对象 |
正则表达式等特殊对象也会丢失 | 如 RegExp |
2. 递归实现深拷贝(基础实现)
示例:
function deepClone(obj) {if (obj === null || typeof obj !== "object") return obj;const copy = Array.isArray(obj) ? [] : {};for (const key in obj) {if (obj.hasOwnProperty(key)) {copy[key] = deepClone(obj[key]);}}return copy;
}
优点:
- 可以处理对象、数组
- 可扩展性强
缺点:
- 不处理循环引用会栈溢出
- 无法复制函数、
Date
、RegExp
等特殊对象
3. 使用第三方库(推荐)
(1) lodash
的 cloneDeep
npm install lodash
import _ from "lodash";const obj = { a: 1, b: { c: 2 } };
const copy = _.cloneDeep(obj);
(2) structuredClone
(浏览器原生 API,现代浏览器支持)
const obj = { a: 1, b: { c: 2 } };
const copy = structuredClone(obj);
✅ 支持:
Date
、Map
、Set
、ArrayBuffer
、Error
等
❌ 不支持:函数、undefined
、某些循环引用
二、处理循环引用的深拷贝实现
function deepClone(obj, visited = new Map()) {if (obj === null || typeof obj !== "object") return obj;if (visited.has(obj)) {return visited.get(obj); // 防止循环引用}const copy = Array.isArray(obj) ? [] : {};visited.set(obj, copy);for (const key in obj) {if (obj.hasOwnProperty(key)) {copy[key] = deepClone(obj[key], visited);}}return copy;
}
三、不同深拷贝方式对比
方法 | 支持类型 | 循环引用 | 函数支持 | 日期支持 | 正则支持 | 性能 | 推荐场景 |
---|---|---|---|---|---|---|---|
JSON.parse | ✅ 对象/数组 | ❌ | ❌ | ❌ | ❌ | ⭐⭐⭐⭐ | 简单对象,无特殊类型 |
递归实现 | ✅ 对象/数组 | ❌(需手动处理) | ❌ | ❌ | ❌ | ⭐⭐ | 学习用途 |
lodash.cloneDeep | ✅ 多种类型 | ✅ | ✅ | ✅ | ✅ | ⭐⭐⭐⭐⭐ | 生产环境 |
structuredClone | ✅ 多种类型 | ✅ | ❌ | ✅ | ✅ | ⭐⭐⭐⭐ | 浏览器端 |
手动封装(带类型判断) | ✅ 自定义 | ✅(可实现) | ✅(可实现) | ✅(可实现) | ✅(可实现) | ⭐⭐⭐ | 定制化需求 |
四、深拷贝的注意事项
点 | 说明 |
---|---|
循环引用 | 必须使用 Map 或 WeakMap 缓存已拷贝对象 |
特殊对象 | 如 Date 、RegExp 、Map 、Set 需要单独处理 |
函数 | 通常不需要拷贝,直接返回原引用即可 |
Symbol 类型键 | 需要用 Reflect.ownKeys 获取 |
原型链上的属性 | 通常不需要拷贝,除非特别要求 |
性能优化 | 深拷贝可能影响性能,应避免频繁调用 |