浅拷贝和深拷贝详解

99 阅读2分钟

浅拷贝

创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

Object.assign:方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 展开运算符是一个es6特性,它提供了一种非常方便的方式来执行浅拷贝,这与 Object.assign ()的功能相同。

// Object.assign
let obj1 = { name: "jack", person: { age: 11, color: "red" } };
let obj2 = Object.assign({}, obj1);
obj2.person.age = 22;
obj2.name = "rose";
// console.log(obj1); // { name: 'jack', person: { age: 22, color: 'red' } }
// console.log(obj2); // { name: 'rose', person: { age: 22, color: 'red' } }

深拷贝

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。

JSON.parse(JSON.stringify(object)) 这个方法有几个问题:

  1. 会忽略undefined
  2. 会忽略symbol
  3. 不能序列化函数
  4. 不能解决循环引用的对象
  5. 不能处理正则
  6. 不能正确处理new Date()
// 不能处理 undefined、Symbol、function、正则
let person = {
  a: undefined,
  b: Symbol("jack"),
  c: function foo() {},
  d: /'123'/,
};
JSON.parse(JSON.stringify(person)); // { d: {} }

// 不能正确处理 new Date()
new Date(); // 2023-05-29T07:24:53.918Z
JSON.parse(JSON.stringify(new Date())); // 2023-05-29T07:24:53.923Z

// 不能处理循环引用
let obj = {
  a: 1,
  b: {
    c: 2,
    d: 3,
  },
};
obj.a = obj.b;
obj.b.c = obj.a;
JSON.parse(JSON.stringify(obj)); // Converting circular structure to JSON

/**
 * @desc: 递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
 * @param {*} obj
 * @param {*} hash
 * @example: 
 */
function deepClonePlus(obj, hash = new WeakMap()) {
  if (obj == null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  // 当obj为 {} 或 [], 使用new obj.constructor()可以处理不同的类型
  let cloneObj = new obj.constructor();
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClonePlus(obj[key], hash);
    }
  }
  return cloneObj;
}