js 深浅拷贝

120 阅读3分钟

刚刚写上一篇的时候突然想起了深浅拷贝的问题,而我的记忆里又不好,所以还是继续记录一下。另外想问一下,记忆力不好怎么办,换脑子有用吗?我打算找戴夫收购一个僵尸的脑子。

浅拷贝(一层):仅仅是复制了引用,彼此之间的操作会互相影响

let obj1 = {
    name: "可达鸭",
    age: 18
};
let obj2 = obj1;

obj2.name = "五花肉";

console.log(obj1) // {name: "五花肉", age: 18}
console.log(obj2) // {name: "五花肉", age: 18}

为什么会发生这种情况,因为当我们定义 let obj2 = obj1; 时他们都来自于同一个对象的内存地址,换句话说,obj1 此时跟 obj2 是同一个对象,所以改变一个就会改变另一个,这个时候就引出了一个比较有意思的问题

let obj1 = {
    name: "可达鸭",
    age: 18
};
let obj2 = obj1;
let obj3 = {
    name: "可达鸭",
    age: 18
};

console.log(obj1 == obj2)  // true
console.log(obj1 === obj2) // true
console.log(obj1 == obj3)  // false
console.log(obj1 === obj3) // false

是不是挺有意思的, obj1 和 obj3 定义的东西明明完全相同,但是即不相等更不全等,这是因为他们的内存地址不同,所以才导致这两个不会相等,更加不会全等,但是 obj1 和 obj2 引用的是同一个内存地址,所以这两个既相等也全等。

深拷贝(多层):在堆中重新分配内存,不同的地址,相同的值,互不影响

但是我平常假如这么定义了,一般都是我不想改 obj1 所以才要定义 let obj2 = obj1 ,不然我干嘛不直接用 obj1 呢。那么要怎么样深拷贝。首先我们要看一下下面的一个例子

let a = 1;
let b = a;

    b = 2
    
    console.log(a, b) // 1, 2

这里我们能看到,假如我们复制的是基本类型的话,改变是不会有联动的

  • 基本类型:基本类型值在内存中占据固定大小,保存在栈内存中
  • 引用类型:引用类型的值是对象,保存在堆内存中,而栈内存保存的对象的变量标识符和对象存储在堆内存中的存储地址

当他们需要复制时

  • 基本类型:从一个变量向另外一个新变量复制基本类型的值,会创建这个值的一个副本,并将该副本复制给新变量
  • 引用类型:从一个变量向另一个新变量复制引用类型的值,其实复制的是指针,最终两个变量最终都指向同一个对象

所以这里我们知道了,假如将引用类型里面的值转变为基本类型,那么就可以达到深拷贝的效果。

    let data = {
        id: 1,
        name: "可达鸭",
        age: "35",
        child: {
          job: "子栏目",
          arr: [1, 2, 3, 4, 5, 6],
          parent: {
            title: "这已经到了第三层",
          },
        },
      };
      
      function copy(target, map = new Map()) {
          if (typeof target === "object") {
            let copyTarget = Array.isArray(target) ? [] : {};
            if (map.get(target)) {
              return map.get(target);
            }
            map.set(target, copyTarget);
            for (const key in target) {
              copyTarget[key] = copy(target[key], map);
            }
            return copyTarget;
          } else {
            return target;
          }
        }
        let copyData = copy(data);
        copyData.child.parent.title = "我在这里改变一下最内层的值";
        console.log(copyData, data);
        // 这里我就不把打印的展示出来了,因为太长了
      

看到上面的代码应该能明白,我们只需要循环赋值,使用基本类型的复制方式就可以起到深拷贝的效果,因为不确定对象有多少层,所以需要增加一个递归,判断还是引用类型时,继续循环进入内部即可,另外这里的map 用于检查是否有复制过的对象,有就直接返回即可

到这里差不多就结束了,别问我为什么写的又臭又长,因为我没有脑子