# 13.深拷贝,浅拷贝
https://www.jianshu.com/p/1c142ec2ca45
# 浅拷贝
# 1. for···in 只循环第一层
// 只复制第一层的浅拷贝
function simpleCopy(obj1) {
var obj2 = Array.isArray(obj1) ? [] : {};
for (let i in obj1) {
obj2[i] = obj1[i];
}
return obj2;
}
var obj1 = {
a: 1,
b: 2,
c: {
d: 3,
},
};
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
alert(obj1.a); // 1
alert(obj2.a); // 3
alert(obj1.c.d); // 4
alert(obj2.c.d); // 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 2. Object.assign 方法
var obj = {
a: 1,
b: 2,
};
var obj1 = Object.assign({}, obj);
obj1.a = 3;
console.log(obj.a); // 3
1
2
3
4
5
6
7
2
3
4
5
6
7
# 3. 直接用=赋值
let a = [0, 1, 2, 3, 4],
b = a;
console.log(a === b);
a[0] = 1;
console.log(a, b);
1
2
3
4
5
2
3
4
5
# 实现深拷贝的方法
# 1. 递归实现(hasOwnProperty+typeof)
function deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {};
// 解决循环饮用问题
if (map.get(obj)) {
return map.get(obj);
}
map.set(obj, res);
if (obj && typeof obj === "object") {
for (key in obj) {
// 使用for in 主要是兼顾考虑数组和对象的情况,如果从性能方面考虑,则数组:forEach,对象:Object.keys(target).forEach
if (obj.hasOwnProperty(key)) {
//判断ojb子元素是否为对象,如果是,递归复制
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = deepClone(obj[key]);
} else {
//如果不是,简单复制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
let a = [1, 2, 3, 4],
b = deepClone(a);
a[0] = 2;
console.log(a, b);
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
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
# 2. 通过 JSON 对象来实现深拷贝
缺点:
- 如果 obj 里面有时间对象,则 JSON.stringify 后再 JSON.parse 的结果,时间将只是字符串的形式,而不是对象的形式
- 如果 obj 里有 RegExp(正则表达式的缩写)、Error 对象,则序列化的结果将只得到空对象;
- 如果 obj 里有函数,undefined,则序列化的结果会把函数或 undefined 丢失;
- 如果 obj 里有 NaN、Infinity 和-Infinity,则序列化的结果会变成 null
- 如果对象中存在循环引用的情况也无法正确实现深拷贝;
function deepClone2(obj) {
var _obj = JSON.stringify(obj),
objClone = JSON.parse(_obj);
return objClone;
}
1
2
3
4
5
2
3
4
5
# 3. 最终版深拷贝
- 对于日期和正则的类型时, 进行处理 new 一个新的
- 对 a: { val: a } 这种循环引用时, 使用以 weakMap 进行巧妙处理
- 使用 Reflect.ownKeys 返回一个由目标对象自身的属性键组成的数组,
- 对于剩下的拷贝类型为 object 和 function 但不是 null 进行递归操作,
- 对于除了上述的类型外直接进行"key"的赋值操作。 细节处理:
- 利用 getOwnPropertyDescriptors 返回指定对象所有自身属性(非继承属性)的描述对象
- 将得到的属性利用 Object.create 进行继承原型链
- 对于 a: { val: a} 循环引用使用 weakMap.set 和 get 进行处理。 实现代码
- 用map代替weekmap也可以,主要是从性能考虑,使用weekmap
WeakMap 对象是一组键值对的集合,其中的键是弱引用对象,而值可以是任意。因为 WeakMap 是弱引用类型,可以有效防止内存泄漏, 作为检测循环引用很有帮助,如果存在循环,则引用直接返回 WeakMap 存储的值。 Reflect.ownKeys == Object.getOwnPropertyNames(target) contact (Object.getOwnPropertySymbols(target)。 Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。 Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组
const isComplexDataType = (obj) =>
(typeof obj === "object" || typeof obj === "function") && obj !== null;
const deepClone = function(obj, hash = new WeakMap()) {
if (obj.constructor === Date) return new Date(obj); // 日期对象直接返回一个新的日期对象
if (obj.constructor === RegExp) return new RegExp(obj); //正则对象直接返回一个新的正则对象
//如果循环引用了就用 weakMap 来解决
if (hash.has(obj)) return hash.get(obj);
let allDesc = Object.getOwnPropertyDescriptors(obj);
//遍历传入参数所有键的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc);
//继承原型链
hash.set(obj, cloneObj);
for (let key of Reflect.ownKeys(obj)) {
// 针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys 方法
cloneObj[key] =
isComplexDataType(obj[key]) && typeof obj[key] !== "function" ?
deepClone(obj[key], hash) :
obj[key];
// typeof obj[key] !== 'function')
}
return cloneObj;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22