1.浅拷贝
创建新对象来接受要重新复制或引用的对象值;若对象属性是基本数据类型,复制的就是基本类型的值给新对象;若属性是引用数据类型,复制的就是内存中的地址
1.1 object.assign
object.assign是ES6中object的一个方法,该方法可以用于Js对象的合并等多个用途,其中一个用途就是浅拷贝
Object.assign(target,...sources)
注意:
不会拷贝对象的继承属性
不会拷贝对象的不可枚举属性
可以拷贝Symbol类型属性
1.2 扩展运算符
let cloneObj={...obj};
扩展运算符和object.assign有同样的缺陷,实现的功能也差不多,需要注意的是扩展运算符不能复制不可迭代的Symbol类型值(但是可以复制Symbol类型值的对对象,因为可以迭代),object.assign方法也是同样的道理
如果属性都是基本类型的值,使用扩展运算符进行浅拷贝会更加方便
1.3 concat 拷贝数组
连接一个含有引用类型的数组的时候,需要注意修改原数组中的元素的属性,因为它会影响拷贝之后连接的数组
1.4 slice拷贝数组
仅仅只针对数组类型,且该方法会返回一个新的数组对象,该方法的两个参数用来决定原数组截取的开始和结束位置,不会影响改变原始数组,但是数组元素是引用类型的话还是会影响到原数组
1.5 Object.getOwnPropertyDescriptors()方法拷贝数组
Object.getOwnPropertyDescriptors()
方法的另一个用处,是配合Object.create()
方法,将对象属性克隆到一个新对象。这属于浅拷贝。
const clone = Object.create(Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj));
// 或者
const shallowClone = (obj) => Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
1.6 总结
根据上述浅拷贝的办法不难看出,浅拷贝的限制在于只能拷贝一层对象,如果存在对象的嵌套便没有任何的办法,所以才会有深拷贝来解决这一问题
2.深拷贝
2.1 JSON.parse( JSON.stringify() )序列化和反序列
将需要拷贝的对象进行JSON字符串化,再用parse解析出来赋值给另一个变量,从而实现深拷贝
有所弊端:
JSON在执行字符串化的时候会先进行一个JSON格式化,获得安全的JSON值,所以非安全的JSON值会被丢弃掉(undefined、function、symbol三种属于非安全JSON值,然后还有该对象的属性循环赋值该对象),而set、map这种数据格式的对象也没有被正确的处理,而是处理成了一个空对象
如下的闭环结构也不行:
// 测试数据
var data = {
name: 'foo',
child: null,
}
data.child = data
有所优点:
能很好地处理多嵌套的对象
2.2 迭代递归方法
其实我觉得最好的解决拷贝赋值问题的方法就是自己敲迭代递归解决
迭代递归1.0
function deepCopy(data){
if(typeof data !== 'object' || data === null){
throw new TypeError('传入参数不是对象')
}
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){
newData[value] = new Set([...currentDataValue]);
}else if(currentDataValue instanceof Map){
newData[value] = new Map([...currentDataValue]);
}else {
newData[value] = deepCopy(currentDataValue);
}
});
return newData;
}
这种方法算是能比较全面地覆盖到大部分的数据结构与类型,但也不能完全覆盖,比如new Number(),因此在实现深拷贝这一方法的时候可以具体情况具体分析
而且上述实现没有考虑到闭环结构
优化后如下:
迭代递归2.0
function deepCopy(data,hash = new WeakMap()){
if(typeof data !== 'object' || data === null){
throw new TypeError('传入参数不是对象')
}
//基本数据类型的值和函数直接复制拷贝
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){
newData[value] = new Set([...currentDataValue]);
}else if(currentDataValue instanceof 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.在内部递归之前请注意使用了hash.set()方法来将待拷贝对象的属性的引用和存储在上一个待拷贝对象的WeakMap中;所以,如果是循环引发产生的闭环,那么这两个引用由于指向的是同一个对象,所以会进入if(hash.has())语句判断,从而退出函数,而不会一直递归调用导致栈溢出。