返回

高性能实现Javascript深度拷贝

前端

高性能版深度拷贝

在JavaScript中,由于引用类型的存在,使得我们在使用的过程中,很容易发生一些可预知的副作用,比如下面的例子:

const a = {
  name: 'John',
  age: 30
};

const b = a;

b.name = 'Mary';

console.log(a.name); // Mary

显然,上面的例子中a的属性name也被修改了。对于上面的数据结构来说,也许我们浅拷贝就足够了,但是如果数据结构变得更加复杂,比如下面的例子:

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

const b = a;

b.address.street = '456 Elm Street';

console.log(a.address.street); // 456 Elm Street

在这个例子中,b对a的地址对象的修改也影响了a的地址对象。这是因为JavaScript中的对象是引用类型,这意味着它们在内存中存储为指向另一个内存位置的指针。当我们对一个对象进行修改时,我们实际上只是修改了这个指针所指向的内存位置。

为了避免这种副作用,我们可以使用深度拷贝来创建一个对象的副本,这个副本与原对象完全独立,对副本所做的任何修改都不会影响原对象。

在JavaScript中,有多种方法可以实现深度拷贝。一种简单的方法是使用JSON.stringify()和JSON.parse()方法。这两个方法可以将一个对象转换成一个JSON字符串,然后再将JSON字符串转换成一个对象。这种方法很简单,但是它也有一个缺点,就是它不能拷贝函数和Symbol值。

另一种实现深度拷贝的方法是使用递归函数。这种方法可以拷贝任何类型的对象,包括函数和Symbol值。但是,这种方法的缺点是它可能非常慢,尤其是当对象非常大的时候。

为了解决上述两种方法的缺点,我们可以使用一种叫做“标记-清除”的算法来实现深度拷贝。这种算法首先给对象打上标记,然后遍历对象,复制所有标记过的对象。这种方法既简单又高效,而且它可以拷贝任何类型的对象。

以下是使用“标记-清除”算法实现的深度拷贝函数:

function deepCopy(obj) {
  const seen = new WeakMap();

  function copy(obj) {
    if (typeof obj === 'object' && obj !== null) {
      if (seen.has(obj)) {
        return seen.get(obj);
      }
      seen.set(obj, obj);

      if (Array.isArray(obj)) {
        return obj.map(copy);
      }

      if (obj instanceof Date) {
        return new Date(obj);
      }

      if (obj instanceof RegExp) {
        return new RegExp(obj);
      }

      const newObj = {};
      for (const key in obj) {
        newObj[key] = copy(obj[key]);
      }

      return newObj;
    }

    return obj;
  }

  return copy(obj);
}

这个函数可以拷贝任何类型的对象,包括函数、Symbol值和循环引用。而且,它的性能也非常高,即使对于非常大的对象也能在短时间内完成拷贝。

深度拷贝在JavaScript中非常有用,它可以帮助我们避免引用类型的副作用。我们可以使用JSON.stringify()和JSON.parse()方法、递归函数或“标记-清除”算法来实现深度拷贝。