开发的过程中经常会遇到需要拷贝对象的时候,因为对象是引用型数据类型,在赋值的过程记录的是对象的地址,所以如果多个变量指向的是同一个内存地址,改变一个变量的属性,所有的变量都会收到影响,在对象的拷贝过程中,需要用到深拷贝。
1 定一个变量
var copy;
定义一个变量不进行赋值,因为copy的类型不一定是对象,也可能是数组或者其它的。
2 考虑基本数据类型
if (null == values || "object" != typeof values) return values;
如果类型是null,undefined,string, boolean,number直接返回原值就行。
3 考虑是Date
Date是用来创建时间对象的,实质上也是对象。所以拷贝的过程中也需要摆脱原对象引用地址。这里就需要重新创建一个date实例,然后跟原对象值保持一致。
if (values instanceof Date) {
copy = new Date();
copy.setTime(values.getTime());
return copy;
}
4 考虑数组
一个对象的属性也可能是数组,数组也是对象,如果直接复制依然会保留对原数组的引用。所以这里也需要新创建一个数组。由于数组中的元素也可能是任何数据类型,这里可以对它使用递归:
if (values instanceof Array) {
copy = [];
for (var i = 0, len = values.length; i < len; i++) {
copy[i] = deepClone(values[i]);
}
return copy;
}
5 考虑对象 如果值还是对象,需要遍历每一个key,这里用的for in遍历。对象的value也可能是任何数据型,所以这里用上了递归。遍历的过程中看到这样一段代码:values.hasOwnProperty(attr)。这么做是出于性能的考虑,for in循环不仅会遍历对象本身的属性,还会遍历原型上可枚举的属性,加上这个判断就避开了原型上的属性。
if (values instanceof Object) {
copy = {};
for (var attr in values) {
if (values.hasOwnProperty(attr)) copy[attr] = deepClone(values[attr]);
}
return copy;
}
考虑循环引用,下面的写法出现了循环引用:如果直接使用上面的方法拷贝,会报错,超出了最大执行栈。
var obj = {};
obj.a = new Date('2020-01-27');
obj.b = obj;
可以使用数据缓存解决循环引用问题。var map = new WeakMap()的可以只接受对象,value可以是任意值。当遇到下面代码时,就说明存在循环引用对象,代码无需向下执行,直接返回存储好的数据就行。
if(map.has(value)) {
return map.get(value);
}
总结起来就是:
function wrapDeepClone(value) {
var map = new WeakMap()
function deepClone(value) {
var copy, existObj; // key只能是对象类型
if(!value || typeof value !== 'object') return value;
if(value instanceof Date) {
copy = new Date();
copy.setTime(value.getTime());
return copy;
}
if(map.has(value)) {
return map.get(value);
}
if(value instanceof Array) {
copy = [];
map.set(value, copy);
for(var i = 0; i < value.length; i++) {
copy[i] = deepClone(value[i]);
}
return copy;
}
if(value instanceof Object) {
copy = {};
map.set(value, copy);
for(var key in value) {
if(value.hasOwnProperty(key)) {
copy[key] = deepClone(value[key])
}
}
return copy;
}
}
return deepClone(value)
}
var copyObj = wrapDeepClone(obj);
console.log(copyObj);