返回

JavaScript 复制:精准掌控深浅复制,一文读懂

前端

在 JavaScript 的世界里,我们经常需要处理各种各样的数据,其中对象类型的数据尤为常见。当我们需要复制一个对象时,很容易掉入“浅复制”和“深复制”的陷阱。你可能会想,不就是复制一个对象嘛,有什么难的?但实际上,JavaScript 中的对象复制远比我们想象的要复杂。

我们先来看一个简单的例子,假设你有一个对象,里面存储了一个人的信息,包括姓名、年龄和地址:

const person1 = {
  name: 'Alice',
  age: 30,
  address: {
    street: 'Example Street',
    city: 'New York',
  },
};

现在,你想创建一个 person2,它的内容和 person1 完全一样。你可能会直接这样写:

const person2 = person1;

看起来很简单,对吧?但是,这种方式实际上只是创建了一个新的引用,person2person1 指向的是同一个内存地址。这意味着,如果你修改了 person2 的属性,person1 的属性也会跟着改变。这就是所谓的“浅复制”。

举个例子,如果你修改 person2 的地址:

person2.address.city = 'Los Angeles';

你会发现,person1 的地址也变成了 Los Angeles!这是因为 person2.addressperson1.address 指向的是同一个地址对象。

那么,如何才能创建一个真正的副本,也就是“深复制”呢?深复制意味着创建一个全新的对象,它的属性值和原始对象相同,但它们指向的是不同的内存地址。

一种常用的深复制方法是利用 JSON.parse()JSON.stringify() 函数:

const person2 = JSON.parse(JSON.stringify(person1));

这种方法先将 person1 转换为 JSON 字符串,然后再将 JSON 字符串解析成一个新的对象 person2。这样,person2 就拥有了和 person1 相同的属性值,但它们是完全独立的两个对象。

除了 JSON 方法,我们还可以使用递归的方式来实现深复制。递归函数会遍历对象的每个属性,如果属性值是对象,就继续递归调用自身,直到所有属性都被复制为止。

function deepClone(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  const newObj = Array.isArray(obj) ? [] : {};
  for (const key in obj) {
    if (Object.hasOwnProperty.call(obj, key)) {
      newObj[key] = deepClone(obj[key]);
    }
  }

  return newObj;
}

const person2 = deepClone(person1);

这种方法可以处理更复杂的嵌套对象,但它的性能可能会比 JSON 方法略低。

在实际开发中,我们应该根据具体情况选择合适的深复制方法。如果对象结构比较简单,可以使用 JSON 方法;如果对象结构复杂,或者对性能要求较高,可以使用递归方法。

值得一提的是,一些第三方库,例如 Lodash,也提供了深复制的功能,例如 _.cloneDeep() 函数。这些库通常经过了优化,性能更好,使用起来也更方便。

常见问题解答

1. 什么情况下需要使用深复制?

当你需要创建一个对象的副本,并且希望修改副本不会影响原始对象时,就需要使用深复制。例如,在 Redux 中,我们通常需要对状态进行深复制,以避免意外修改原始状态。

2. JSON 方法和递归方法有什么区别?

JSON 方法简单易用,但它只能处理可以被序列化为 JSON 字符串的对象。递归方法可以处理更复杂的嵌套对象,但它的性能可能会略低。

3. 深复制的性能开销大吗?

深复制需要遍历对象的所有属性,并创建新的对象,因此它的性能开销会比浅复制大。在实际开发中,我们应该根据具体情况选择合适的复制方式,避免不必要的性能浪费。

4. 如何判断一个对象是否被深复制了?

你可以修改副本的属性,然后检查原始对象的属性是否也发生了改变。如果原始对象的属性没有改变,说明副本是深复制的;如果原始对象的属性也发生了改变,说明副本是浅复制的。

5. 除了 JSON 方法和递归方法,还有其他深复制的方法吗?

是的,还有一些其他的深复制方法,例如使用 Object.assign() 函数或者第三方库提供的函数。选择哪种方法取决于你的具体需求和项目的复杂度。