009-深拷贝与浅拷贝原理+手撕

深拷贝与浅拷贝:原理、实现与最佳实践

核心概念解析

浅拷贝(Shallow Copy)

  • 定义:创建一个新对象,新对象的属性值是对原对象属性值的引用
  • 特点
    • 只复制第一层属性
    • 嵌套对象与原对象共享内存
    • 修改嵌套对象会影响原对象
  • 内存示意图
    原始对象: { a: 1, b: { c: 2 } }
    浅拷贝对象: { a: 1, b: [引用地址] }

    原始对象的b属性 ←┘

深拷贝(Deep Copy)

  • 定义:创建一个完全独立的新对象,递归复制所有层级的属性
  • 特点
    • 复制对象的所有层级
    • 新对象与原对象完全隔离
    • 修改新对象不会影响原对象
  • 内存示意图
    原始对象: { a: 1, b: { c: 2 } }
    深拷贝对象: { a: 1, b: { c: 2 } } // 全新的内存地址

常用方法对比

浅拷贝方法

方法 示例 说明
扩展运算符 const copy = { ...obj } ES6+ 推荐方式
Object.assign() const copy = Object.assign({}, obj) 合并对象实现浅拷贝
Array.prototype.slice() const copy = arr.slice() 数组浅拷贝
Array.from() const copy = Array.from(arr) 类数组转数组实现浅拷贝
手动遍历 见下方实现 自定义浅拷贝逻辑

深拷贝方法

方法 示例 优点 缺点
JSON.parse/stringify JSON.parse(JSON.stringify(obj)) 简单快速 丢失函数/Symbol/undefined/循环引用等
Lodash.cloneDeep _.cloneDeep(obj) 功能完整,处理各种边界情况 需引入外部库
结构化克隆 structuredClone(obj) 浏览器原生API,支持循环引用 Node环境不支持,不支持函数/原型链
手动递归实现 见下方实现 完全可控,支持自定义处理 实现复杂度高

手写实现

浅拷贝函数实现

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
function shallowCopy(source) {
// 处理非对象类型
if (source === null || typeof source !== 'object') {
return source;
}

// 处理数组
if (Array.isArray(source)) {
return [...source];
}

// 处理普通对象
const copy = {};
for (const key in source) {
// 只复制自身属性(非原型链属性)
if (source.hasOwnProperty(key)) {
copy[key] = source[key];
}
}

return copy;
}

// 测试用例
const original = { a: 1, b: { c: 2 } };
const copy = shallowCopy(original);

console.log(copy.b === original.b); // true(共享嵌套对象)

深拷贝函数实现(基础版)

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
function deepCopy(source) {
// 基本类型直接返回
if (source === null || typeof source !== 'object') {
return source;
}

// 处理数组
if (Array.isArray(source)) {
return source.map(item => deepCopy(item));
}

// 处理普通对象
const copy = {};
for (const key in source) {
if (source.hasOwnProperty(key)) {
copy[key] = deepCopy(source[key]);
}
}

return copy;
}

// 测试用例
const original = {
a: 1,
b: { c: 2 },
d: new Date(),
e: /regex/g,
f: function() { console.log('hello') }
};

const copy = deepCopy(original);

console.log(copy.b === original.b); // false(独立嵌套对象)
console.log(copy.d instanceof Date); // true
console.log(copy.e instanceof RegExp); // true
console.log(typeof copy.f); // 'function'

深拷贝函数实现(进阶版)

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
function deepCopyAdvanced(source, map = new WeakMap()) {
// 基本类型直接返回
if (source === null || typeof source !== 'object') {
return source;
}

// 处理循环引用
if (map.has(source)) {
return map.get(source);
}

// 处理特殊对象
switch (true) {
case source instanceof Date:
return new Date(source);

case source instanceof RegExp:
return new RegExp(source);

case source instanceof Map:
return new Map(Array.from(source, ([key, val]) =>
[deepCopyAdvanced(key, map), deepCopyAdvanced(val, map)]
);

case source instanceof Set:
return new Set(Array.from(source, item =>
deepCopyAdvanced(item, map))
);

case Array.isArray(source):
const arrCopy = [];
map.set(source, arrCopy);
source.forEach((item, index) => {
arrCopy[index] = deepCopyAdvanced(item, map);
});
return arrCopy;

default:
// 处理普通对象和类实例
const objCopy = Object.create(
Object.getPrototypeOf(source)
);
map.set(source, objCopy);

// 复制Symbol属性
const symbolKeys = Object.getOwnPropertySymbols(source);
const allKeys = [...Object.keys(source), ...symbolKeys];

allKeys.forEach(key => {
objCopy[key] = deepCopyAdvanced(source[key], map);
});

return objCopy;
}
}

// 测试用例
const original = {
a: 1,
b: { c: 2 },
d: new Date(),
e: /regex/g,
f: function() { return this.a; },
g: new Map([['key', 'value']]),
h: new Set([1, 2, 3]),
i: Symbol('unique'),
j: [4, 5, { k: 6 }]
};

// 添加循环引用
original.self = original;
original.j.push(original.j);

const copy = deepCopyAdvanced(original);

// 验证
console.log(copy !== original); // true
console.log(copy.b !== original.b); // true
console.log(copy.d instanceof Date); // true
console.log(copy.e instanceof RegExp); // true
console.log(copy.f()); // 1(函数正常执行)
console.log(copy.g instanceof Map); // true
console.log(copy.h instanceof Set); // true
console.log(copy.i === original.i); // true(Symbol相同)
console.log(copy.self === copy); // true(循环引用保持)
console.log(copy.j[3] === copy.j); // true(循环引用保持)

特殊场景处理

1. 函数拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 函数拷贝策略
if (typeof source === 'function') {
// 方案1:直接引用(共享函数)
// return source;

// 方案2:创建新函数(保留this绑定)
return function(...args) {
return source.apply(this, args);
};

// 方案3:完整克隆(复杂)
// const fnStr = source.toString();
// return new Function('return ' + fnStr)();
}

2. 原型链处理

1
2
3
4
// 保持原型链
const objCopy = Object.create(
Object.getPrototypeOf(source)
);

3. 不可枚举属性

1
2
3
4
5
6
7
8
9
// 复制不可枚举属性
const props = Object.getOwnPropertyDescriptors(source);
for (const key of Reflect.ownKeys(props)) {
Object.defineProperty(
copy,
key,
deepCopyAdvanced(props[key], map)
);
}

4. 循环引用检测

1
2
3
4
5
6
7
8
// 使用WeakMap检测循环引用
const map = new WeakMap();

if (map.has(source)) {
return map.get(source);
}

map.set(source, copy);

性能优化策略

1. 减少递归层级

1
2
3
4
// 扁平化数据结构处理
if (depth > MAX_DEPTH) {
return Array.isArray(source) ? [...source] : {...source};
}

2. 特殊对象优化

1
2
3
4
5
6
7
8
9
10
11
12
// TypedArray高效处理
if (source instanceof ArrayBuffer) {
return source.slice(0);
}

if (ArrayBuffer.isView(source)) {
return new source.constructor(
source.buffer.slice(0),
source.byteOffset,
source.byteLength / source.BYTES_PER_ELEMENT
);
}

3. 缓存机制

1
2
3
4
5
6
7
8
9
10
11
12
// 缓存已处理对象
const cache = new WeakMap();

function cachedDeepCopy(source) {
if (cache.has(source)) {
return cache.get(source);
}

const copy = deepCopyAdvanced(source);
cache.set(source, copy);
return copy;
}

应用场景分析

适合浅拷贝的场景

场景 原因 示例
配置对象合并 只需顶层属性覆盖 Object.assign(config, overrides)
状态快照(简单状态) 状态对象无嵌套结构 Vue的data对象
函数参数传递 避免修改原始对象 function process(data) { const local = {...data} }
数组切片操作 只需部分数组元素 const page = items.slice(0, 10)

适合深拷贝的场景

场景 原因 示例
状态管理(Redux) 需要完整不可变状态树 Redux reducer中的状态更新
复杂对象序列化 需要完整对象结构 保存到本地存储的复杂状态
避免污染原始数据 操作数据副本不影响源数据 表格编辑临时副本
跨线程通信(Web Worker) 需要序列化所有数据 worker.postMessage(structuredClone(data))

现代API:structuredClone

浏览器原生深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const original = { 
a: 1,
b: { c: 2 },
d: new Date(),
e: new Set([1, 2, 3])
};

try {
const copy = structuredClone(original);
console.log(copy.b !== original.b); // true
console.log(copy.d instanceof Date); // true
} catch (err) {
console.error('不支持structuredClone', err);
}

支持特性

类型 支持 说明
基本类型 number, string, boolean等
Array/Object 普通对象和数组
Date
RegExp
Map/Set
ArrayBuffer
Blob/File
ImageData
函数 抛出DataCloneError异常
DOM节点 抛出DataCloneError异常
原型链 只复制自身属性
循环引用 正确处理循环引用

总结与最佳实践

选择策略

1
2
3
4
5
6
7
8
9
10
graph TD
A[需要拷贝] --> B{是否有嵌套对象}
B -->|是| C{需要完全独立}
B -->|否| D[使用浅拷贝]
C -->|是| E[使用深拷贝]
C -->|否| D
E --> F{环境支持}
F -->|现代浏览器| G[structuredClone]
F -->|Node/复杂需求| H[Lodash.cloneDeep]
F -->|自定义需求| I[手写深拷贝]

最佳实践

  1. 优先使用原生API

    • 浅拷贝:扩展运算符 {...obj}
    • 深拷贝:structuredClone(浏览器环境)
  2. 复杂场景用成熟库

    1
    2
    import { cloneDeep } from 'lodash-es';
    const copy = cloneDeep(original);
  3. 避免JSON暴力转换

    1
    2
    // 不推荐 - 丢失类型信息
    const copy = JSON.parse(JSON.stringify(original));
  4. 循环引用要处理

    • 手动实现时使用WeakMap跟踪
    • 使用库函数自动处理
  5. 性能敏感场景注意

    • 大对象深拷贝可能阻塞主线程
    • 考虑增量复制或Web Worker

终极方案推荐

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
/**
* 生产环境推荐方案
* 1. 浏览器环境:使用structuredClone
* 2. Node环境:使用Lodash的cloneDeep
* 3. 特殊需求:自定义深拷贝函数
*/

function safeDeepCopy(obj) {
// 浏览器环境使用原生API
if (typeof structuredClone === 'function') {
return structuredClone(obj);
}

// Node环境使用Lodash
if (typeof require === 'function') {
try {
const { cloneDeep } = require('lodash');
return cloneDeep(obj);
} catch (e) {
// 降级处理
}
}

// 降级方案:基础深拷贝
return JSON.parse(JSON.stringify(obj));
}