一、核心区别速览
| 对比维度 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 复制层级 | 只复制第一层 | 递归复制所有层级 |
| 嵌套对象 | 共享内存,修改互相影响 | 独立内存,修改互不影响 |
| 性能 | 快 | 慢(数据量大时) |
| 实现难度 | 简单 | 复杂(需处理各种边界情况) |
二、代码演示区别
javascript
// 原始对象
const original = {
name: '张三',
age: 18,
address: {
city: '北京',
district: '朝阳区'
},
hobbies: ['读书', '游泳']
};
// ========== 浅拷贝 ==========
const shallow = { ...original };
shallow.name = '李四'; // ✅ 修改第一层,不影响原对象
shallow.address.city = '上海'; // ❌ 修改嵌套对象,原对象也被改了!
console.log(original.address.city); // '上海' - 被影响了!
console.log(original.name); // '张三' - 没被影响
// ========== 深拷贝 ==========
const deep = JSON.parse(JSON.stringify(original));
deep.name = '王五'; // ✅ 不影响原对象
deep.address.city = '广州'; // ✅ 不影响原对象
deep.hobbies.push('跑步'); // ✅ 不影响原对象
console.log(original.address.city); // '北京' - 没被影响
console.log(original.hobbies); // ['读书', '游泳'] - 没被影响
三、浅拷贝的 6 种方式
3.1 对象浅拷贝
javascript
const obj = { a: 1, b: { c: 2 } };
// 1. 展开运算符(最常用)
const copy1 = { ...obj };
// 2. Object.assign
const copy2 = Object.assign({}, obj);
// 3. Object.create + 遍历(少见)
const copy3 = Object.create(Object.getPrototypeOf(obj));
Object.assign(copy3, obj);
3.2 数组浅拷贝
javascript
const arr = [1, 2, { a: 3 }];
// 1. 展开运算符
const copy1 = [...arr];
// 2. slice()
const copy2 = arr.slice();
// 3. concat()
const copy3 = arr.concat();
// 4. Array.from()
const copy4 = Array.from(arr);
// 5. 循环遍历
const copy5 = [];
for (let i = 0; i < arr.length; i++) {
copy5[i] = arr[i];
}
3.3 特殊情况
javascript
// 类数组对象
const args = { 0: 'a', 1: 'b', length: 2 };
const copy = Array.prototype.slice.call(args);
// Map 和 Set(只浅拷贝)
const map = new Map([['key', { value: 1 }]]);
const mapCopy = new Map(map);
四、深拷贝的完整方案
4.1 JSON 大法(最常用,但有缺陷)
javascript
const deepCopy = JSON.parse(JSON.stringify(obj));
✅ 优点:
-
简单易用,一行代码
-
性能好(V8 引擎优化)
❌ 缺点:
javascript
const obj = {
// 1. 函数会被忽略
fn: function() { console.log('hello'); },
// 2. undefined 会被忽略
undef: undefined,
// 3. Symbol 会被忽略
sym: Symbol('id'),
// 4. 日期会变成字符串
date: new Date(), // 变成 "2024-01-01T00:00:00.000Z"
// 5. 正则变成空对象
reg: /abc/g, // 变成 {}
// 6. NaN、Infinity 变成 null
nan: NaN, // 变成 null
inf: Infinity, // 变成 null
// 7. 循环引用会报错
circular: null
};
obj.circular = obj; // ❌ TypeError: Converting circular structure to JSON
console.log(JSON.parse(JSON.stringify(obj)));
// 结果:{ date: "2024-...", reg: {}, nan: null, inf: null }
// fn、undef、sym 都不见了
4.2 手写递归深拷贝(面试常考)
javascript
function deepClone(obj, hash = new WeakMap()) {
// 处理 null 和基本类型
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
// 处理日期
if (obj instanceof Date) {
return new Date(obj);
}
// 处理正则
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 处理数组和对象
const cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
// 递归复制所有属性(包括 Symbol 和不可枚举属性)
Reflect.ownKeys(obj).forEach(key => {
cloneObj[key] = deepClone(obj[key], hash);
});
return cloneObj;
}
// 测试
const obj = {
name: '张三',
age: 18,
address: { city: '北京' },
date: new Date(),
reg: /abc/g,
fn: () => {},
sym: Symbol('id')
};
obj.self = obj; // 循环引用
const cloned = deepClone(obj);
console.log(cloned.address !== obj.address); // true
console.log(cloned.date instanceof Date); // true
console.log(cloned.reg instanceof RegExp); // true
console.log(cloned.self === cloned); // true(循环引用正确处理)
4.3 lodash 库(生产环境推荐)
javascript
import _ from 'lodash';
const deepCopy = _.cloneDeep(obj);
4.4 structuredClone(现代浏览器原生支持)
javascript
// 2022 年新增的原生深拷贝 API
const deepCopy = structuredClone(obj);
// 支持大多数类型,但仍不支持函数、Symbol、DOM 节点等
4.5 MessageChannel(异步深拷贝)
javascript
function deepCloneAsync(obj) {
return new Promise((resolve) => {
const { port1, port2 } = new MessageChannel();
port1.onmessage = (e) => resolve(e.data);
port2.postMessage(obj);
});
}
// 使用
const cloned = await deepCloneAsync(obj);
五、各方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JSON 大法 | 简单、快 | 不支持函数/undefined/Symbol/循环引用 | 纯数据对象 |
| 手写递归 | 完全可控、支持所有类型 | 代码量大、需处理边界 | 面试、特殊需求 |
| lodash | 功能完善、稳定 | 需要引入库 | 生产环境 |
| structuredClone | 原生、快 | 兼容性要求高、不支持函数 | 现代浏览器 |
| MessageChannel | 支持循环引用 | 异步、性能差 | 特殊场景 |
六、面试高频问题
Q1:浅拷贝和深拷贝的区别?
答:
浅拷贝只复制对象的第一层属性,对于嵌套的引用类型数据,新旧对象仍然共享同一块内存,修改嵌套数据会互相影响。深拷贝会递归复制所有层级,完全开辟新的内存空间,新旧对象彻底独立,修改互不影响。
Q2:JSON.parse(JSON.stringify()) 有什么缺陷?
答:
无法复制函数、undefined、Symbol 等类型(会被忽略)
日期对象会变成字符串
正则对象会变成空对象
NaN、Infinity 会变成 null
循环引用会报错
Q3:如何实现一个完美的深拷贝?
答:
需要考虑以下情况:
基本类型直接返回
处理循环引用(使用 WeakMap)
特殊对象:Date、RegExp、Map、Set 等
函数(一般不需深拷贝,可直接返回原引用)
数组和普通对象的递归复制
Symbol 和不可枚举属性的处理
Q4:浅拷贝有哪些方式?
答:
对象:
{...obj}或Object.assign({}, obj)数组:
[...arr]、arr.slice()、arr.concat()、Array.from(arr)