众所周知,所有的高级语言都会有自己的方法来读写复杂数据类型,在C语言中我们通常使用的便是指针操作。而在JavaScript中,这就不得不提值类型和引用类型了。像number、boolean和string等都是值类型,而其他的复合类型包括函数就是引用类型。两者的拷贝方式截然不同。
浅拷贝的弊端
如果我们用直接赋值的方式来拷贝引用类型,那么我们只是声明了指向同一块内存的新的引用,不会真正的在内存中复制两个对象出来。很明显无法实现我们想要的功能。
let obj ={
property: 1,
}
obj.property;// 1
let obj1 = obj;
obj1.property = 2;
obj1.property;// 2
obj.property;// 2
常用的深拷贝方法
我们想让系统创建两个一模一样的对象,应该如何去做呢?这里介绍了一些深拷贝的方法。
使用JSON
我们都知道,JSON是跨语言传递参数的一种很好的规范,因此我们也可以使用JSON来进行对象的深拷贝。只需要对你想要的对象使用JSON.stringify,然后让新的对象等于JSON.parse。
这种方法也有一些问题。如果你的对象有嵌套的话(例如对象的某些属性是另一个对象)或者存在循环引用的话,JSON是无法处理的。
let obj ={
property: 1,
}
obj.property;// 1
let objJSON = JSON.stringify(obj);
let obj1 = JSON.parse(objJSON);
obj1.property = 2;
obj1.property;// 2
obj.property;// 1
使用Object.assign
JavaScript也有自带的函数来实现这种功能,Object.assign函数可以将source对象深拷贝到target对象中。但是这对于嵌套引用依旧不会奏效。
let obj ={
property: 1,
val:{
property: 2,
}
}
obj.property;// 1
obj.val;// {property:2}
let obj1 = {};
Object.assign(obj1,obj);
obj1.property = 2;
obj.val.property =1;
obj1.val;// {property:1}
obj.val;// {property:1}
obj.property;// 1
obj1.property;// 2
使用递归函数
解决嵌套的最好方法便是通过递归函数来解决。传入一个函数,然后递归式的把旧对象的所有属性赋值给新对象。在这其中也需要注意一些要点。
- 创建新对象时不要用
{},最好使用new obj.constructor,这样才能保持相同的原型链继承关系。 - 在遍历属性的时候需要用
hasOwnProperty,因为普通的for循环也会遍历原型链上的属性。 - 递归调用最好使用
arguments.callee来指代函数本身,否则如果要修改函数名的话会非常的麻烦。 - 递归调用时也需要判断类型,避免不必要的开销。
let obj ={
property: 1,
val:{
property: 2,
}
}
function deepCopy(obj){
let newObj = new obj.constructor();
for(let key in obj){
if(obj.hasOwnProperty(key)){
let val = obj[key];
if(typeof val === 'object'){
newObj[key] = arguments.callee(val);
}else{
newObj[key] = val;
}
}
}
return newObj;
}
obj.property;// 1
obj.val;// {property:2}
let obj1 = deepCopy(obj);
obj1.property = 2;
obj.val.property =1;
obj1.val;// {property:2}
obj.val;// {property:1}
obj.property;// 1
obj1.property;// 2