深浅拷贝区别

一、深浅拷贝的核心区别

深浅拷贝的本质差异在于:是否复制对象的 “嵌套引用类型属性”(如对象、数组、函数等),核心对比如下:

特性 浅拷贝(Shallow Copy) 深拷贝(Deep Copy)
拷贝层级 仅拷贝 “第一层” 属性,嵌套引用类型只复制引用(地址) 递归拷贝所有层级,嵌套引用类型会创建新的独立对象
内存指向 嵌套对象 / 数组与原对象共享同一块内存 所有层级的对象 / 数组都有独立内存,互不影响
修改影响 修改拷贝后的嵌套属性,会同步影响原对象 修改拷贝后的任意属性,都不会影响原对象
实现复杂度 简单(无需递归) 复杂(需递归处理所有嵌套层级,处理特殊类型)
性能 效率高,占用内存少 效率低,占用内存多(递归创建新对象)

二、直观示例:理解深浅拷贝的差异

基础场景:嵌套对象的拷贝

1
2
3
4
5
6
7
// 原对象(包含嵌套引用类型)
const original = {
name: "张三",
age: 18,
hobbies: ["读书", "运动"], // 嵌套数组(引用类型)
address: { city: "北京" } // 嵌套对象(引用类型)
};
1. 浅拷贝示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 浅拷贝:仅复制第一层,嵌套属性仍共享引用
const shallowCopy = { ...original };

// 修改第一层基本类型属性 → 不影响原对象
shallowCopy.name = "李四";
console.log(original.name); // 张三(第一层独立)

// 修改嵌套数组 → 原对象同步变化(共享引用)
shallowCopy.hobbies.push("游戏");
console.log(original.hobbies); // ["读书", "运动", "游戏"]

// 修改嵌套对象 → 原对象同步变化(共享引用)
shallowCopy.address.city = "上海";
console.log(original.address.city); // 上海
2. 深拷贝示例
1
2
3
4
5
6
7
8
9
10
// 深拷贝:递归复制所有层级,嵌套属性独立
const deepCopy = JSON.parse(JSON.stringify(original));

// 修改嵌套数组 → 原对象无影响
deepCopy.hobbies.push("游戏");
console.log(original.hobbies); // ["读书", "运动"]

// 修改嵌套对象 → 原对象无影响
deepCopy.address.city = "上海";
console.log(original.address.city); // 北京

三、浅拷贝的常见实现方式

1. 对象浅拷贝

  • **扩展运算符 { ...obj }**(ES6+,最常用);
  • Object.assign({}, obj)(ES6+);
  • 手动遍历第一层属性赋值。
1
2
3
4
5
const obj = { a: 1, b: { c: 2 } };
// 方式1:扩展运算符
const copy1 = { ...obj };
// 方式2:Object.assign
const copy2 = Object.assign({}, obj);

2. 数组浅拷贝

  • **扩展运算符 [...arr]**;
  • arr.slice()(不传参数默认拷贝整个数组);
  • arr.concat()(空参数拼接,返回新数组)。
1
2
3
4
5
6
7
const arr = [1, [2, 3]];
// 方式1:扩展运算符
const copy1 = [...arr];
// 方式2:slice
const copy2 = arr.slice();
// 方式3:concat
const copy3 = arr.concat();

四、深拷贝的常见实现方式

1. 简易方案:JSON.parse(JSON.stringify(obj))(有局限性)

  • 优点:简单易用,无需手写递归;

  • 缺点

    :无法处理以下类型:

    • 函数、undefinedSymbol(会被忽略);
    • 循环引用(如 obj.a = obj,会报错);
    • 特殊对象(Date 转为字符串、RegExp 转为空对象、Map/Set 转为空对象)。
1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
a: 1,
b: () => {}, // 函数:会被忽略
c: undefined, // undefined:会被忽略
d: Symbol("s"), // Symbol:会被忽略
e: new Date(), // Date:转为字符串
f: /abc/, // RegExp:转为空对象
g: [1, 2]
};
const copy = JSON.parse(JSON.stringify(obj));
console.log(copy);
// { a: 1, e: "2025-12-25T08:00:00.000Z", f: {}, g: [1,2] }

2. 手动递归实现(自定义深拷贝)

处理基础类型、引用类型、循环引用等场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function deepClone(target, map = new WeakMap()) {
// 1. 处理基础类型和 null/undefined
if (target === null || typeof target !== "object") {
return target;
}

// 2. 处理循环引用(避免无限递归)
if (map.has(target)) {
return map.get(target);
}

// 3. 处理 Date/RegExp 特殊对象
if (target instanceof Date) {
return new Date(target);
}
if (target instanceof RegExp) {
return new RegExp(target);
}

// 4. 处理数组/普通对象
const cloneTarget = Array.isArray(target) ? [] : {};
map.set(target, cloneTarget); // 缓存原对象,解决循环引用

// 5. 递归拷贝所有属性
for (const key in target) {
if (target.hasOwnProperty(key)) { // 仅拷贝自身属性(不拷贝原型链)
cloneTarget[key] = deepClone(target[key], map);
}
}

// 6. 处理 Map/Set(可选扩展)
if (target instanceof Map) {
const cloneMap = new Map();
map.set(target, cloneMap);
target.forEach((v, k) => cloneMap.set(k, deepClone(v, map)));
return cloneMap;
}
if (target instanceof Set) {
const cloneSet = new Set();
map.set(target, cloneSet);
target.forEach(v => cloneSet.add(deepClone(v, map)));
return cloneSet;
}

return cloneTarget;
}

// 测试:支持循环引用、函数、特殊对象
const obj = { a: 1, b: [2, 3], c: new Date() };
obj.d = obj; // 循环引用
const copy = deepClone(obj);
console.log(copy.b === obj.b); // false(嵌套数组独立)
console.log(copy.d === copy); // true(循环引用处理正常)

3. 第三方库(推荐,成熟稳定)

  • Lodash 的 _.cloneDeep()

    :工业级深拷贝,支持所有类型(函数、循环引用、特殊对象等),是前端主流方案。

    1
    2
    3
    4
    import _ from 'lodash';
    const obj = { a: 1, b: [2, 3], c: () => {} };
    const copy = _.cloneDeep(obj);
    console.log(copy.b === obj.b); // false

五、深浅拷贝的适用场景

浅拷贝适用场景

  1. 仅需拷贝 “无嵌套引用类型” 的简单对象 / 数组;
  2. 追求性能,无需独立嵌套属性(如临时复用第一层属性);
  3. 快速创建对象副本,且不修改嵌套属性。

深拷贝适用场景

  1. 拷贝包含嵌套对象 / 数组的复杂数据(如接口返回的复杂数据、表单状态);
  2. 需要修改拷贝后的数据,且不影响原数据;
  3. 处理包含循环引用、特殊对象(Date/RegExp/Map/Set)的场景。

六、核心总结

场景 选择拷贝方式 推荐实现
简单对象 / 数组(无嵌套) 浅拷贝 扩展运算符 { ...obj }/[...arr]
复杂对象(有嵌套 / 特殊类型) 深拷贝 Lodash _.cloneDeep()
临时使用、追求性能 浅拷贝 Object.assign / slice
数据独立、避免副作用 深拷贝 自定义递归(简单场景)/_.cloneDeep(复杂场景)