# 深浅拷贝的区别
假设B复制了A,当修改B时,看A是否会发生变化,如果A也跟着变了,说明这是浅拷贝,如果A没变,那就是深拷贝。
在JavaScript中深拷贝只针对引用类型的值(如array,object),而基本类型的值不存在深浅拷贝一说(如number,string,boolean),因此这里只考虑引用类型的数据:object和array。
# 浅拷贝的实现
# 直接赋值
const arr1 = [1, 2, 3];
const arr2 = arr1;
console.log(arr2); // [1, 2, 3]
arr2[1] = 22;
console.log(arr1); // [1, 22, 3]
console.log(arr2); // [1, 22, 3]
1
2
3
4
5
6
2
3
4
5
6
# 深拷贝的实现
# 只有基本类型的数组
- 创建新数组,循环往里添加。
slice:const arr2 = arr1.slice(0);。concat:const arr2 = arr1.concat();。Array.from():const arr2 = Array.from(arr1);。- 扩展运算符:
const arr2 = [...arr1];。
# 只有基本类型的对象
- 创建新对象,循环往里添加。
Object.assign():const obj2 = Object.assign({}, obj1)。- 扩展运算符:
const obj2 = { ...obj1 };。
# 数组或对象里面还有引用数据
JSON.parse(JSON.stringify())const obj1 = { name: 'obj1', child: { name: 'child', } }; const obj2 = JSON.parse(JSON.stringify(obj1));1
2WARNING
JSON.parse(JSON.stringify())实现深拷贝对象(object)有以下弊端:- 时间对象
Date,格式化之后变成字符串。 RegExp、Error对象,格式化后变成空对象{}。- 函数、
undefined、Symbol格式化后键值对将丢失。 NaN、Infinity、-Infinity,格式化后变成null。JSON.stringify()只能序列化对象的可枚举的自有属性,如果对象中的属性值是由构造函数生成的,格式化会丢弃对象的constructor。- 如果对象中存在循环引用的情况也无法正确实现深拷贝。
- 时间对象
递归
/* eslint-disable no-restricted-syntax */ /* eslint-disable no-use-before-define */ /** * @param {*} value * @returns {string} */ function getType(value) { // "[object Object]" return Object.prototype.toString.call(value).slice(8, -1).toLocaleLowerCase(); } /** * @param {Array} value * @param {Map} cache * @returns {Array} */ function cloneArray(value, cache) { const result = []; cache.set(value, result); value.forEach((el) => { result.push(deepClone(el, cache)); }); return result; } /** * @param {RegExp} value * @param {Map} cache * @returns {RegExp} */ function cloneRegexp(value, cache) { const result = new RegExp(value.source, value.global); cache.set(value, result); return result; } /** * @param {Object} value * @param {Map} cache * @returns {Object} */ function cloneObject(value, cache) { const result = {}; cache.set(value, result); const keys = Object.getOwnPropertyNames(value).concat(Object.getOwnPropertySymbols(value)); keys.forEach((key) => { result[key] = deepClone(value[key], cache); }); return result; } /** * @param {Function} value * @param {Map} cache * @returns {Function} */ function cloneFunction(value, cache) { // eslint-disable-next-line no-new-func const result = new Function(`return ${value.toString()}`)(); cache.set(value, result); return result; } /** * @param {Date} value * @param {Map} cache * @returns {Date} */ function cloneDate(value, cache) { const result = new Date(value); cache.set(value, result); return result; } /** * @param {Map} value * @param {Map} cache * @returns {Map} */ function cloneMap(value, cache) { const result = new Map(); cache.set(value, result); for (const [key, val] of value.entries()) { result.set(key, deepClone(val), cache); } return result; } /** * @param {Set} value * @param {Map} cache * @returns {Set} */ function cloneSet(value, cache) { const result = new Set(); cache.set(value, result); for (const val of value) { result.add(deepClone(val), cache); } return result; } /** * @param {*} value * @param {Map} cache * @returns {*} */ function cloneNormal(value, cache) { cache.set(value, value); return value; } /** * @description 深拷贝 * @author fengchunqi * @date 2021-06-17 14:35:13 * @param {*} data * @param {Map} cache * @returns {*} */ function deepClone(data, cache = new Map()) { if (cache.has(data)) { return data; } const type = getType(data); switch (type) { case 'array': return cloneArray(data, cache); case 'regexp': return cloneRegexp(data, cache); case 'object': return cloneObject(data, cache); case 'function': return cloneFunction(data, cache); case 'date': return cloneDate(data, cache); case 'map': return cloneMap(data, cache); case 'set': return cloneSet(data, cache); default: // number、string、boolean、undefined、null、ymbol、bigint // 其他 promise htmlcollection nodelist... return cloneNormal(data, cache); } }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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146// 测试 const obj = { Number: 1, String: 'string', Boolean: true, Undefined: undefined, Null: null, Function: function aaa() { console.log('aaa'); }, Date: new Date(2021, 5, 17), RegExp: /a-z/gi, Array: [1, 2, 3, 4, 5], Object: { name: 'object', value: 111, }, }; const copy = deepClone(obj);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19MessageChannelfunction deepClone(value) { return new Promise((resolve, reject) => { const { port1, port2 } = new MessageChannel(); port1.postMessage(value); port2.onmessage = (msg) => { resolve(msg.data); }; }); } deepClone(obj).then((val) => { console.log(val); });1
2
3
4
5
6
7
8
9
10
11
12
13注意:经测试发现
MessageChannel的方式支持循环引用,但是不支持拷贝函数(可能是考虑到函数拷贝(new Function)的安全问题)。