js 浅拷贝和深拷贝
# 浅拷贝
- Object.assign()
Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
Object.assign()是对第一层属性依次拷贝,如果第一层的属性是基本数据类型,就拷贝值;如果是引用数据类型,就拷贝内存地址。
Object.assign 可以用来处理数组,但是会把数组视为对象。
Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]
- Array.prototype.concat()和 Array.prototype.slice() (适用数组)
Array 的 slice 和 contact 方法都不会修改原数组,而是会返回一个对原数组进行浅拷贝的新数组。这两种方法同 Object.assign()一样,都是对第一层属性依次拷贝,如果第一层的属性是基本数据类型,就拷贝值;如果是引用数据类型,就拷贝内存地址。
# 深拷贝
基本数据类型
# 函数深拷贝
- 通过 eval()
var b = eval("0,"+a);//方法一
- 通过 new Function()
var c = new Function("return "+a)();//方法二
上面两种方法无法拷贝函数的静态属性
函数的处理要简单说下,我认为克隆函数是没有必要的其实,两个对象使用一个在内存中处于同一个地址的函数也是没有任何问题的,如下是 lodash 对函数的处理:
const isFunc = typeof value == "function";
if (isFunc || !cloneableTags[tag]) {
return object ? value : {};
}
2
3
4
# symbol 深拷贝
- Object.getOwnPropertySymbols(obj)
# Date 日期对象深拷贝
if (data instanceof Date) {
return new Date(data);
}
2
3
# RegExp 正则对象深拷贝
if (data instanceof RegExp) {
return new RegExp(data);
}
2
3
# 深拷贝方案
# 1. JSON.parse
大部分需要深拷贝的场景,都可以使用以下代码:
JSON.parse(JSON.stringify(object))
!!!但这种办法会忽略 undefined、function、symbol 和循环引用的对象
缺点:这种方法可以实现数组和对象和基本数据类型的深拷贝,但不能处理函数。因为 JSON.stringify()方法是将一个 javascript 值转换我一个 JSON 字符串,不能接受函数。其他影响如下:
- 拷贝 Date 引用类型会变成字符串
- 键值变成空对象:对象的值中为 Map、Set、RegExp 这几种类型。
- undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略
- 如果对象中有 NAN、infinity、-infinity,那么序列化的结果会变成 null
- 无法拷贝:不可枚举属性、对象的原型链。
- 循环引用的会抛出错误
# 2. MessageChannel(消息通道)
使用 MessageChannel(消息通道)进行深拷贝
MessageChannel 的消息在发送和接收的过程需要序列化和反序列化。利用这个特性,我们可以实现深拷贝
但这个深拷贝只能解决 undefined 和循环引用对象的问题,对于 Symbol 和 function 依然束手无策
function deepCopy(obj) {
return new Promise((resolve) => {
const { port1, port2 } = new MessageChannel();
port2.onmessage = (ev) => resolve(ev.data);
port1.postMessage(obj);
});
}
2
3
4
5
6
7
# 3. 函数库
lodash,提供_.cloneDeep 用来做深拷贝
# 4. 自己封装函数
递归实现深拷贝 缺陷:当遇到两个互相引用的对象时,会出现死循环的情况,从而导致爆栈。
function deepCopy(obj) {
var result = Array.isArray(obj) ? [] : {};
if (obj && typeof obj === "object") {
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
// 思考:这句是否有必要?
if (obj[i] && typeof obj[i] === "object") {
result[i] = deepCopy(obj[i]);
} else {
result[i] = obj[i];
}
}
}
}
return result;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
深拷贝函数最终版
支持基本数据类型、Symbol、Array、Object、原型链、RegExp、Date、 Map、Set 、Function、不可枚举属性 类型及解决循环引用问题
function deepClone(data) {
// 可遍历类型 Map Set Object Array
const typeMap = "[object Map]";
const typeSet = "[object Set]";
const typeObject = "[object Object]";
const typeArray = "[object Array]";
// 非原始类型的 不可遍历类型 Date RegExp Function
const typeDate = "[object Date]";
const typeRegExp = "[object RegExp]";
const typeFunction = "[object Function]";
// 非原始类型的 不可遍历类型的 集合(原始类型已经被过滤了不用再考虑了)
const notForType = [typeDate, typeRegExp, typeFunction];
// 是否是引用类型
const isObject = (target) => {
if (target === null) {
return false;
} else {
const type = typeof target;
return type === "object" || type === "function";
}
};
// 获取标准类型
const getType = (target) => {
return Object.prototype.toString.call(target);
};
/*
* 1、处理原始类型 Number String Boolean Symbol Null Undefined
* 2、处理不可遍历类型 Date RegExp Function
* 3、处理循环引用情况 WeakMap
* 4、处理可遍历类型 Set Map Array Object
* */
const clone = (target, map = new WeakMap()) => {
// 处理原始类型直接返回(Number BigInt String Boolean Symbol Undefined Null)
if (!isObject(target)) {
return target;
}
// 处理不可遍历类型
const type = getType(target);
if (notForType.includes(type)) {
switch (type) {
case typeDate:
// 日期
return new Date(target);
case typeRegExp:
// // 正则
// var flags = "";
// flags += target.global ? "g" : "";
// flags += target.multiline ? "m" : "";
// flags += target.ignoreCase ? "i" : "";
// const regExp = new RegExp(target.source, flags);
// regExp.lastIndex = target.lastIndex; // lastIndex 表示每次匹配时的开始位置
// return regExp;
// 正则
return new RegExp(target);
case typeFunction:
// 函数
return target;
default:
return target;
}
}
// 用于返回
let result;
// 处理循环引用
if (map.get(target)) {
// 已经放入过map的直接返回
return map.get(target);
}
// 处理可遍历类型
switch (type) {
case typeSet:
// Set
result = new Set();
map.set(target, result);
target.forEach((item) => {
result.add(clone(item, map));
});
return result;
case typeMap:
// Map
result = new Map();
map.set(target, result);
target.forEach((item, key) => {
result.set(key, clone(item, map));
});
return result;
case typeArray:
// 数组
result = [];
map.set(target, result);
target.forEach((item, index) => {
result[index] = clone(item, map);
});
return result;
case typeObject:
// 对象
result = {};
map.set(target, result);
[
...Object.keys(target),
...Object.getOwnPropertySymbols(target),
].forEach((item) => {
result[item] = clone(target[item], map);
});
return result;
default:
return target;
}
};
return clone(data);
}
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
参考:https://www.cnblogs.com/jsunwang/p/13410952.html (opens new window)
解决 Symbol 问题:https://www.cnblogs.com/piaobodewu/p/13767736.html (opens new window)