对象的深浅拷贝方法
js类型介绍:
首先js的数据值按照类型主要分为两大类,基本数据类型和引用数据类型。基本数据类型包括 Undefined、Null、Number、String、Boolean、Symbol ;引用数据类型则为Object,那些 Array、Set、Map 数据也属于Object。
对象的浅拷贝有哪些方式:
浅拷贝是对象共用一个内存地址,对象的变化相互影响。比如常见的赋值引用就是浅拷贝
let objName= {'name': '张三', 'age': '18'};
let copyObjName = objName;
copyObjName .age = '25';
console.log('objName', objName); // objName{ name: '张三', age: '25' }
console.log('copyObjName ', copyObjName ); // copyObjName { name: '张三', age: '25' }
对象的深拷贝有哪些方式:
简单理解深拷贝是将对象放到一个新的内存中,两个对象的改变不会相互影响。
const objName= {'name': '张三', grade: {'chi': '1', 'eng': '2'} };
let copyObjName = Object.assign({}, objName);
copyObjName .name = '李四';
copyObjName .grade.chi = '3';
console.log('新 objName', objName); // { name: '张三', grade: { chi: '3', eng: '2' } }
从例子中可以看出,改变复制对象的name 和 grade.chi ,源对象的name没有变化,但是grade.chi却被改变了。因此我们可以看出 Object.assign() 拷贝的只是属性值,假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
也就是说,对于Object.assign()而言, 如果对象的属性值为简单类型(string, number),通过Object.assign({},objName);得到的新对象为‘深拷贝’;如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的。这是Object.assign()特别值得注意的地方。多说一句,Object.assign({}, obj1, obj2); 对于 obj1和 obj2 之间相同的属性是直接覆盖的,如果属性值为对象,是不会对对象之间的属性进行合并的。
const objName= {'name': '张三', grade: {'chi': '1', 'eng': '2'} };
let copyObjName = JSON.parse(JSON.stringify(objName));
copyObjName .name = '李四';
copyObjName .grade.chi = '3';
console.log('新 objName', objName); // { name: '张三', grade: { chi: '3', eng: '2' } }
可以看到改变 copyObjName 并没有改变原始对象,实现了基本的深拷贝。
但是用 JSON.parse() 和 JSON.stringify() 会有一个问题。
JSON.parse() 和 JSON.stringify() 能正确处理的对象只有Number、String、Array等能够被 json 表示的数据结构,因此函数这种不能被 json 表示的类型将不能被正确处理。比如
const objName= {'name': '张三', grade: {'chi': '1', 'eng': '2'}, 'fun': function() {console.log('fun')}};
let copyObjName = JSON.parse(JSON.stringify(objName));
copyObjName .name = '李四';
copyObjName .grade.chi = '3';
console.log('copyObjName ', copyObjName ); //{ name: '李四', grade: { chi: '3', eng: '2' } }
可以看出,经过转换之后,function丢失了,因此 JSON.parse() 和 JSON.stringify() 还是需要谨慎使用。
递归方式实现对象的深拷贝:
function deepCopy(data, hash = new WeakMap()) {
if(typeof data !== 'object' || data === null){
throw new TypeError('传入参数不是对象')
}
// 判断传入的待拷贝对象的引用是否存在于hash中
if(hash.has(data)) {
return hash.get(data)
}
let newData = {};
const dataKeys = Object.keys(data);
dataKeys.forEach(value => {
const currentDataValue = data[value];
// 基本数据类型的值和函数直接赋值拷贝
if (typeof currentDataValue !== "object" || currentDataValue === null) {
newData[value] = currentDataValue;
} else if (Array.isArray(currentDataValue)) {
// 实现数组的深拷贝
newData[value] = [...currentDataValue];
} else if (currentDataValue instanceof Set) {
// 实现set数据的深拷贝
newData[value] = new Set([...currentDataValue]);
} else if (currentDataValue instanceof Map) {
// 实现map数据的深拷贝
newData[value] = new Map([...currentDataValue]);
} else {
// 将这个待拷贝对象的引用存于hash中
hash.set(data,data)
// 普通对象则递归赋值
newData[value] = deepCopy(currentDataValue, hash);
}
});
return newData;
}
借助了个存储对象的容器WeakMap,思路就是,初次调用deepCopy时,参数会创建一个WeakMap结构的对象,这种数据结构的特点之一是,存储键值对中的健必须是对象类型。
1、首次调用时,weakMap为空,不会走上面那个if(hash.has())语句,如果待拷贝对象中有属性也为对象时,则将该待拷贝对象存入weakMap中,此时的健值和健名都是对该待拷贝对象的引用。
2、然后递归调用该函数。
3、再次进入该函数,传入了上一个待拷贝对象的对象属性的引用和存储了上一个待拷贝对象引用的weakMap,因为如果是循环引用产生的闭环,那么这两个引用是指向相同的对象的,因此会进入if(hash.has())语句内,然后return,退出函数,所以不会一直递归进栈,以此防止栈溢出。
延伸一下数组的深浅拷贝方式:
浅拷贝数组:
let numArry= [11, 12, 13];
let copyNumArry = numArry;
copyNumArry [0] = '10';
console.log('numArry', numArry); // ['10', 12, 13]
深拷贝数组:
concat() 方法 ,:
连接数组的方法(将多个数组合并成一个数组 )
slice(start, [end]) 方法:
参数:
arrayObj 必选项。一个 Array 对象。
start 必选项。arrayObj 中所指定的部分的开始元素是从零开始计算的下标。
end可选项。arrayObj 中所指定的部分的结束元素是从零开始计算的下标。
说明:
slice 方法返回一个 Array 对象,其中包含了 arrayObj 的指定部分。
slice 方法一直复制到 end 所指定的元素,但是不包括该元素。
如果 start 为负,将它作为 length + start处理,此处 length 为数组的长度。
如果 end 为负,就将它作为 length + end 处理,此处 length 为数组的长度。
如果省略 end ,那么 slice 方法将一直复制到 arrayObj 的结尾。
如果 end 出现在 start 之前,不复制任何元素到新数组中。
Array.from() 方法:
将伪数组变成真实的数组方法
有哪些说的不全面的地方还请多多指教,以上就是简单的总结,还请多点赞加评论