深拷贝、浅拷贝(TS)

539 阅读3分钟

一、深拷贝 浅拷贝

基本数据类型的特点:直接存储在栈(stack)中的数据  
引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里

1. 为什么要用深拷贝

在希望改变**新的数组(对象)**的前提下,而不去改变**原始数组(对象)**

2. 深拷贝

深拷贝会创建一个一模一样的对象,新旧对象**分处于不同的内存空间**,即不会共享内存。
  1. 拷贝第一层级的对象属性或者数组元素。
  2. 递归拷贝所有的对象属性和数组元素。
  3. 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
1)深拷贝方法
1. JSON 序列化JSON.parse(JSON.stringify(Obj))
- JSON序列化   `无法拷贝函数`
- JSON序列化   `会忽略属性值为undefined`

实例:

<script lang="ts" setup>
   import { ref } from "vue";
   // JSON序列化深拷贝
   interface refobj1 {
      data: Array<number>;
      meta: object;
      undefinedData: undefined;
   }
   let RefObj = ref<refobj1>({
      data: [1, 2],
      meta: {
         data: 1,
      },
      undefinedData: undefined,
   });
   //  获取变量refObj的值
   let refObjValue: object = RefObj.value;
   console.log("原始对象", refObjValue);
   //  JSON 序列化
   let JSONline = JSON.parse(JSON.stringify(RefObj.value));
   console.log("JSON序列化浅拷贝", JSONline);
</script>

结果:

ref 定义的对象 是由 Proxy 代理的,所有的值须在 `Target` 中查看
由图知,undefined 属性 在深拷贝时 丢失了

image.png

2. for……in 手写深拷贝函数 (若需要同时拷贝函数,值为undefined的属性)
   //  手写for循环实现深拷贝
   interface refobj2 {
      data: Array<number>;
      meta: object;
      undefinedData: undefined;
      nullValue: null;
   }
   let RefObj2 = ref<refobj2>({
      data: [1, 2],
      meta: {
         data: "2",
      },
      undefinedData: undefined,
      nullValue: null,
   });
   // 重写typeof方法
   function typeOf(obj: any): any {
      const toString: any = Object.prototype.toString;
      const map: any = {
         "[object Boolean]": "boolean",
         "[object Number]": "number",
         "[object String]": "string",
         "[object Function]": "function",
         "[object Array]": "array",
         "[object Date]": "date",
         "[object RegExp]": "regExp",
         "[object Undefined]": "undefined",
         "[object Null]": "null",
         "[object Object]": "object",
      };
      return map[toString.call(obj)];
   }
   // 定义一个深拷贝函数
   function deepClone(data: any): any {
      // 获取传入拷贝函数的数据类型
      const type = typeOf(data);
      // 定义一个返回any类型的数据
      let reData: any;
      // 递归遍历一个array类型数据,
      if (type === "array") {
         reData = [];
         for (let i = 0; i < data.length; i++) {
            reData.push(deepClone(data[i]));
         }
      } else if (type === "object") {
         //递归遍历一个object类型数据
         reData = {};
         for (const i in data) {
            reData[i] = deepClone(data[i]);
         }
      } else {
         // 返回基本数据类型
         return data;
      }
      // 将any类型的数据return出去,作为deepClone的结果
      return reData;
   }
   let obj2: any = deepClone(RefObj2.value);
   // 为true, 表示两个数据是相同(使用同一块内存地址);
   // 为false,则表示深拷贝成功(使用不同块的内存地址)
   console.log(obj2 === RefObj2.value);
   console.log("原始对象", RefObj2.value);
   console.log("新对象", obj2);

结果:

image.png

3. 浅拷贝

浅拷贝只复制某个对象的指针,而不是对象本身,新旧对象还是**共享一个内存块**
  1. 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
  2. 如果拷贝对象的属性是基本类型,那么拷贝的就是属性的值;如果对象属性是一个引用类型(内存地址),那么拷贝的就是内存地址,因此,如果其中一个对象改变这个地址,就会影响到另一个对象。

4. 赋值和浅拷贝的区别

当把一个对象的值赋值给一个新的变量时,赋的其实是该对象在堆栈中的地址,而不是堆栈中的数据,也就是说,两个对象指向的是同一个存储空间,无论改变那个对象的内容,其实都是改变这个存储空间的内容,因此,两个对象的内容是联动的。 浅拷贝是按位拷贝对象,它会创建一个一个新的对象,这个对象有着原始对象属性值的一份精确拷贝。默认拷贝构造函数只是对对象成员逐个依次拷贝,即只复制对象空间而不是复制资源。

1)浅拷贝和赋值实例
   interface shallowcopy {
      name: string;
      age: number;
      langguage: Array<Array<number> | number>;
      obj: {
         name: string;
         age: number;
      };
   }
   interface objassignment {
      name: string;
      age: number;
      langguage: Array<Array<number> | number>;
   }
   let ObjAssignment = ref<objassignment>({
      name: "小米",
      age: 18,
      langguage: [[1, 2], 3, [5, 6]],
   });
   let ShallowCopy = ref<shallowcopy>({
      name: "小红",
      age: 26,
      langguage: [[1, 2], 3, [5, 6]],
      obj: {
         name: "3333",
         age: 444,
      },
   });
   // 对象赋值
   var obj1: any = ObjAssignment.value;
   console.log("原始对象:", ObjAssignment.value);
   console.log("对象赋值→新对象:", obj1);
   // 修改 新对象
   obj1.name = "对象赋值"
   obj1.langguage[0] = [1,2,3,4,5]

   // 浅拷贝
   function shallowCopy(obj: any) {
      var result: any = {};
      var key: string;
      for (key in obj) {
         if (obj[key]) {
            result[key] = obj[key];
         }
      }
      return result;
   }
   let NewShallowCopy = shallowCopy(ShallowCopy.value);
   console.log("原对象:", ShallowCopy.value);
   console.log("浅拷贝新对象:", NewShallowCopy);
   // 修改新对象的值
   NewShallowCopy.obj.name = "浅拷贝";  // 修改浅拷贝结果的属性值
   NewShallowCopy.name = "浅拷贝";    // 修改浅拷贝结果对象值
   NewShallowCopy.langguage[0] = [9999,99999]  // 修改浅拷贝结果的数组值
   console.log("浅拷贝修改后的原对象:", ShallowCopy.value);
   console.log("浅拷贝修改后的新对象:", NewShallowCopy);
1.对象赋值结果

image.png

2.浅拷贝结果

image.png

通过浅拷贝出来的对象里面,如果是基本属性类型的,并没有继续通过proxy代理,而是直接将属性值提取了出来;
如果是一个引用类型(对象、数组等)则直接拿的原来的内存地址,所以还是通过proxy代理的数据。

image.png

后面去修改通过浅拷贝的数据时:
基本属性类型的数据并没有发生改变
而引用类型数据内的内容影响到了原数据内容