前端积累 | 浅拷贝与深拷贝

33 阅读2分钟

众所周知,所有的高级语言都会有自己的方法来读写复杂数据类型,在C语言中我们通常使用的便是指针操作。而在JavaScript中,这就不得不提值类型和引用类型了。像numberbooleanstring等都是值类型,而其他的复合类型包括函数就是引用类型。两者的拷贝方式截然不同。

浅拷贝的弊端

如果我们用直接赋值的方式来拷贝引用类型,那么我们只是声明了指向同一块内存的新的引用,不会真正的在内存中复制两个对象出来。很明显无法实现我们想要的功能。

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