JavaScript 深拷贝

23 年 6 月 15 日 星期四
828 字
5 分钟

深拷贝是前端非常常用的功能,本文将会介绍几种实现方式和相关的库。

实现方式

先序列化后解析

js
JSON.parse(JSON.stringify())

这可能是最简单的方法,首先使用 JSON.stringify 将对象转成 JSON 字符串,然后再使用 JSON.parse 将字符串解析成新的对象。

但是它有以下这些限制:

  • 如果对象里面有 undefinedDateRegExp 等类型,序列化后的结果会丢失原有的类型信息,变成 string 类型。
  • NaNInfinity-Infinity 序列化后会变成 null
  • 如果对象中出现循环引用,会导致代码陷入死循环,从而抛出错误。

因此,它只适用于一些简单的 JavaScript 对象,如果需要深拷贝复杂的对象,最好使用其他方法。

递归

我们可以使用递归来实现深拷贝。对于一个对象,我们可以递归地遍历它的每一个属性,然后将它们复制到一个新的对象中。如果属性的值也是一个对象,我们可以递归地处理它,直到所有属性都被复制到了新的对象。

代码如下:

jsx
function cloneDeep(obj) {
  if (obj === null) return null
  if (typeof obj !== 'object') return obj
  const target = Array.isArray(obj) ? [] : {}
  for (let key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      target[key] = cloneDeep(obj[key])
    }
  }
  return target
}

这样一个简单的深克隆函数就完成了,但是它现在存在一个问题:如果对象中存在循环引用的话,就会报错:Uncaught RangeError: Maximum call stack size exceeded

jsx
function cloneDeep(obj, stack) {
  if (obj === null) return null
  if (typeof obj !== 'object') return obj
  stack = stack || new WeakMap()
  if (stack.has(obj)) return stack.get(obj)
  const target = Array.isArray(obj) ? [] : {}
  stack.set(obj, target)
  for (let key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      target[key] = cloneDeep(obj[key], stack)
    }
  }
  return target
}

开源库

Lodash

lodash 是 JavaScript 中一个非常流行的实用工具库,如果你还没有使用过它,建议自行了解。它其中包括了一个叫做 cloneDeep 的方法,可以深度复制对象,而且它的性能也很高1。使用这个方法,我们可以轻松地实现深拷贝,而且能够处理复杂的对象,包括循环引用和嵌套对象。

klona

klona 是一个超小的高性能克隆函数,它可以安全地处理 ArrayDateMapObjectRegExpSetTypedArray 等数据类型。

其他

还有许多其他处理深克隆的库,例如:deep-copy、clone、clone-deep、rfdc 等等。

Immer 是一个专门用来处理不可变数据的库,它提供了一些非常方便的 API,可以让我们轻松地创建和修改不可变数据,而且它的性能也很高。如果需要处理大量的不可变数据,Immer 是一个不错的选择。

原生 API

structuredClone() 是 HTML5 中提供的一个 API,可以深拷贝一个对象。它能够处理循环引用和不同类型的数据2,包括 DateRegExp 等类型。但是它有以下这些限制:

  • 只能在主线程中使用,不能在 Web Worker 中使用。
  • 不能序列化函数、DOM 节点等类型,更多内容请参考: 结构化克隆算法
  • 在 Safari 中,它不能序列化 ArrayBuffer

因此,它只适用于一些简单的 JavaScript 对象,如果需要深拷贝复杂的对象,最好使用其他方法。

参考

  1. lodash.cloneDeep 中文文档

  2. structuredClone MDN 接口参考

文章标题:JavaScript 深拷贝

文章作者:柃夏chapu

文章链接:https://www.lxchapu.com/posts/deep-copy-and-javascript[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。