Skip to content
前端

JavaScript的深拷贝和浅拷贝

杨少侠

740字约2分钟

前端

2022-09-19

JavaScript 的深拷贝和浅拷贝

JavaScript 中分两种数据类型,一种是基础数据类型,另一种是引用数据类型,深拷贝和浅拷贝是只针对 Object 和 Array 这样的引用数据类型的。

赋值和浅拷贝的区别

  • 由于引用数据类型是存放在堆内存中的,对一个对象的赋值,实际上赋的是该对象的在栈中的地址,而不是堆中的数据。因此这两个对象指向的是同一个堆内的存储空间,无论哪个对象发生改变,存储空间的内容都会相应发生改变,两个对象是联动的。
  • 浅拷贝创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。浅拷贝其实就是解决了单层的赋值问题。

栈内存和堆内存

浅拷贝的实现方式(当拷贝对象只有一层的时候,就是深拷贝)

  1. ...展开运算符
  2. Object.assign()
  3. Array.prototype.slice()

slice()方法返回一个新的数组对象,这一对象是一个由 begin 和 end(不包括 end)决定的原数组的浅拷贝。原始数组不会被改变。

  1. Array.prototype.concat()

深拷贝,拷贝之后两个对象相互独立,互不影响

深拷贝的实现方式

1. JSON.parse(JSON.stringify(Object))

let a = [0, "1", [2, 3]];
let b = JSON.parse(JSON.stringify(a.slice(1)));
console.log(b);
// ["1", [2, 3]]

 缺点:

  1. undefinedsymbol函数这三种情况会直接忽略
  2. 不能序列化函数
  3. 不能解决循环引用的对象
  4. 不能正确处理 new Date()

2. 递归实现

function deepClone(target) {
  if (typeof target === 'object') {
    // 兼容数组
    let cloneTarget = Array.isArray(target) ? [] : {};
    for (const key in target) {
      cloneTarget[key] = deepClone(target[key]);
    }
    return cloneTarget;
  } else {
    return target;
  }
};

3. 解决递归深拷贝的循环引用问题

const target = {
  field1: 1,
  field2: undefined,
  field3: {
    child: 'child',
  },
  field4: [2, 4, 8],
};
target.target = target;

对象的属性间接或直接的引用了自身的情况的情况下,该方法会进入死循环导致栈内存溢出

// 解决循环引用的递归
function clone(target, map = new Map()) {
  if (typeof target === 'object') {
    let cloneTarget = Array.isArray(target) ? [] : {};
    // 额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝
    if (map.get(target)) {
      return map.get(target);
    }
    map.set(target, cloneTarget);
    for (const key in target) {
      cloneTarget[key] = deepClone(target[key], map);
    }
    return cloneTarget;
  } else {
    return target;
  }
};

What can I say?