对于对象深拷贝的一些思考

64 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的第5天。

因为以前没有接触过js,js是现学现卖的,所以可能有不对的地方请大佬指教。

问题的开始

在我们的项目中,我们创建了一些对象,这些对象有着不同的内存地址存储着。我们有些需求复制对象。

let user3 = {  name: 'John',  size: { height: 182, width: 50, }};

字面量赋值

最开始的想法是直接用字面量赋值的方式来进行复制。

let clone = user3;

但是这样是显然不行的,因为js中对象并不是“原始值类型”,这样复制的话是无法复制过来的。以我的理解是user3相当于一个C语言中的指针,它只是把指针给了clone,让它指向该对象的地址。这样并没有实现复制。

用Object.assign()方法实现赋值

所以我查阅了一些资料,发现可以用Object的assign方法实现复制。

let clone = Object.assign({}, user3);

console.log( clone.name ); // John
console.log( clone.size.weight ) // 182

上面代码说明了我们完成了复制,但是紧接着我发现了一个问题。

console.log(user3.size === clone.size); //true
user3.size.height++;                    //改变size对象中的height
console.log(clone.size.height);         //183,也就是说clone只是一个引用变量。

我改变的明明是user3中的size对象的属性,但clone中的size对象的属性也随之改变。所以这个方法并不能完全的把对象属性是对象的值复制过来,到头来还是复制的引用。

经过我的猜测Object.assign()方法大概是这样实现。

for (let objectValue in user3) {    
    clone[objectValue] = user3[objectValue];
}

所以对于属性是对象的话,这样的方法是不行的。

实现真正的深度复制

但是这时候我灵光乍现,突然有个大概的想法,能不能用递归的方式去找对象的属性是否有对象。有的话就去找对象属性 中的 对象属性 是否 有对象。 直到找到一个对象属性的值全为原始类型为止。然后再层层往回return完成复制。接着我就对代码完成了大概的实现。

// 允许克隆多个对象
function cloneDeep(cloneObj, ...permission) {    
    length = permission.length;    
    for (let i = 0; i < length; i++) {        
        cloneObj =  copyObj( cloneObj, permission[i] );    
    }    
        return cloneObj;
}
  1. 首先我使用了扩展运算符来接收将要复制的对象,也就是说此函数是支持多个对象属性复制到一个对象中的。这些对象会存储到permission这个数组中,可以通过数组下标来访问输入的对象。

  2. 接着遍历数字,让每个对象的属性都复制到cloneObj中。

  3. 在循环中会用到copyObj这个函数,实现的方法如下

  4.  function copyObj(dest, src) {    for (let key in src)    {        // 判断当前的属性是否为对象        if (typeof src[key] === 'object') {            // 新创建一个对象来存储src[key]中的值            let temp = {};            // 是对象就再次调用copyObj函数用,直到找到一个对象的所有属性是原始值为止。            buf = copyObj(temp, src[key]);            dest[key] = buf;        } else {            dest[key] = src[key];        }    }    // 当找到一个对象的属性全为原始值并且属性已经添加到dest中后返回引用。    return dest;}
    
  5. 首先我们会遍历src中的键,看看键中有没有对象。

  6. 如果我们找到了对象

  7. 先创建一个空的对象用来存储当前键所对应对象的属性

  8. 然后再次调用copyObj函数,将空对象和当前键所对应对象的属性传入

  9. 函数会将复制完的值返回给buf变量,然后将buf变量的引用传递给目标的相关键就完成了!

  10. 如果我们的值是原始类型,那就直接复制就好了。

    let user4 = {};
    user4 = cloneDeep(user4, user3); //实现深拷贝 user4.size.width++; console.log(user4.size.width); //51 console.log(user3.size.width); //50 成功完成深拷贝

我们的对复制完以后的user4进行操作,将它size属性中的width加1。然后我们打印出user3.size.width的值。发现值是不同的,这就证明它们没有操控同一块内存,我们的深拷贝成功了!