返回

透过深拷贝的表象,解密其本质和衍生

前端

我们常说的深拷贝,其实就是将一个对象及其所有属性和值完全复制一份的新方法。而浅拷贝仅仅是复制对象的引用,并没有复制对象本身。那么,为什么我们需要深拷贝呢?

深拷贝的必要性

我们先来看一个浅拷贝的例子:

const obj1 = {
  name: 'John Doe',
  age: 30,
  address: {
    street: '123 Main Street',
    city: 'Anytown',
    state: 'CA',
  },
};

const obj2 = obj1;

obj2.name = 'Jane Smith';

现在,obj1obj2都指向同一个对象。这意味着对obj2的任何更改都会影响到obj1。这在某些情况下可能并不是我们想要的结果。

例如,我们可能希望将obj1传递给一个函数,并在函数中对obj1进行修改,而不会影响到原始对象。在这种情况下,我们就需要使用深拷贝。

深拷贝的实现方式

有多种方法可以实现深拷贝。最常见的方法之一是使用JSON.parse()JSON.stringify()。这两个函数可以将对象转换为 JSON 字符串,然后再将其解析回对象。这种方法简单易用,但是它有一个缺点:它不能处理循环引用。

如果对象中存在循环引用,那么使用JSON.parse()JSON.stringify()进行深拷贝就会导致无限递归,最终导致栈溢出。

为了处理循环引用,我们可以使用一个称为“标记和复制”的算法。这个算法首先将对象及其所有属性标记为“已访问”。然后,它将对象的所有属性复制到一个新的对象中。最后,它将所有标记为“已访问”的属性复制到新的对象中。

标记和复制算法的示例代码

const deepCopy = (obj) => {
  const visited = new WeakMap();

  const copy = (obj, visited) => {
    if (typeof obj !== 'object' || obj === null) {
      return obj;
    }

    if (visited.has(obj)) {
      return visited.get(obj);
    }

    visited.set(obj, copy);

    const newObj = Array.isArray(obj) ? [] : {};

    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        newObj[key] = copy(obj[key], visited);
      }
    }

    return newObj;
  };

  return copy(obj, visited);
};

这个算法可以处理循环引用,因为它在复制对象之前会先检查对象是否已经标记为“已访问”。如果对象已经标记为“已访问”,那么它就不会再次复制该对象。

深拷贝的优缺点

深拷贝是一种非常有用的工具,它可以帮助我们避免浅拷贝带来的问题。但是,深拷贝也有一些缺点:

  • 性能开销: 深拷贝比浅拷贝需要更多的计算资源,因为它需要复制整个对象及其所有属性和值。
  • 内存开销: 深拷贝会创建新的对象,这会增加内存的使用。

结语

深拷贝是一种非常有用的工具,它可以帮助我们避免浅拷贝带来的问题。但是,深拷贝也有一些缺点,我们需要在使用深拷贝之前权衡这些优缺点。

在实际开发中,我们应该根据具体情况选择使用浅拷贝还是深拷贝。如果对象比较小,而且不存在循环引用,那么浅拷贝就可以满足我们的需求。如果对象比较大,或者存在循环引用,那么我们应该使用深拷贝。