深拷贝在 JavaScript 中的几种实现方式对比

深拷贝在 JavaScript 中的几种实现方式对比

在开发过程中,我们经常需要对对象进行深拷贝,以便创建一个数据的完全独立副本,避免不小心修改原始数据。常见的深拷贝方法有以下几种:
• JSON 序列化法(JSON.stringify/JSON.parse)
• 结构化克隆(structuredClone)
• 自定义深拷贝函数

下面将详细介绍每种方法,并讨论它们在效率和适用场景上的不同。

1. JSON 序列化法

原理与使用

通过 JSON.stringify() 将对象转为 JSON 字符串,再利用 JSON.parse() 还原为新对象。例如:

js 复制代码
const deepCopyJson = (obj) => {
  return JSON.parse(JSON.stringify(obj));
};

优点:

• 实现简单,代码少。

• 对于简单对象(不包含函数、undefined、Symbol、循环引用等)非常有效。

缺点:

• 无法处理特殊数据类型:例如 Date、RegExp、Map、Set、undefined、Infinity 等会丢失或转换不正确。

• 当对象数据量较大时,序列化和反序列化过程可能比较耗时。

• 对于包含循环引用的对象会抛错。

适用场景:
加粗样式 • 对象结构简单,数据格式固定时(例如配置数据或简单的 JSON 数据)。

2. 结构化克隆(structuredClone)

原理与使用

现代浏览器提供了原生的 structuredClone() 方法,利用浏览器内部优化的结构化克隆算法,能够高效地复制多种复杂数据结构。使用示例:

js 复制代码
if (typeof structuredClone === 'function') {
  const newObj = structuredClone(obj);
}

优点:

• 原生支持,效率较高,尤其适用于大型或复杂对象。

• 能够处理更多类型的数据:如 Map、Set、ArrayBuffer、Date 等。

• 内部实现针对循环引用也能较好地处理。

缺点:

• 浏览器兼容性需要注意,老旧环境可能不支持。

• 对于小对象,可能没有明显优势,有时反而比 JSON 方法稍慢(不过整体差异不大)。

适用场景:

• 对象结构复杂或包含特殊数据类型时;

• 对性能有较高要求且运行环境支持该 API 时。

3. 自定义深拷贝函数

原理与使用

在无法使用 JSON 或 structuredClone 的情况下,可以手动编写递归函数来深度拷贝对象。下面是一个简单的实现:

js 复制代码
const deepCopy = (item) => {
  if (item === null || typeof item !== 'object') {
    return item;
  }
  
  if (Array.isArray(item)) {
    return item.map(deepCopy);
  }
  
  const result = {};
  for (const key in item) {
    if (Object.prototype.hasOwnProperty.call(item, key)) {
      result[key] = deepCopy(item[key]);
    }
  }
  return result;
};

const newObj = deepCopy(obj);

优点:

• 灵活性高,可以根据需要扩展以处理更多特殊情况。

• 不依赖环境提供的 API,兼容性好。

缺点:

• 需要手动处理各种边界情况,代码实现和维护较复杂。

• 性能上通常不及浏览器原生实现的 structuredClone。

适用场景:

• 对兼容性有要求的项目或需要定制拷贝逻辑的场景;

• 数据结构较简单,且不频繁调用时。

性能对比与选择建议

性能比较

  • 小型对象:
    JSON 方法通常性能较好,因为序列化过程开销较低,且实现简单。
  • 复杂对象:
    对于包含特殊数据类型或嵌套层次较深的对象,structuredClone 通常表现更优,因为它是浏览器内部实现的,并针对复杂场景做了优化。
  • 自定义函数:
    一般来说,自定义递归实现的性能不及结构化克隆,但可以针对具体需求进行优化。

综合建议:

  1. 优先选择 JSON 方法:当你确定对象数据格式简单,并且不会包含无法序列化的内容时。
  2. 考虑 structuredClone:如果运行环境支持且对象较大或结构复杂,使用 structuredClone() 更能保证数据完整性和性能。
  3. 自定义深拷贝:当需要对数据进行特殊处理或兼容不支持 structuredClone 的环境时,可以采用自定义函数,但要注意性能优化和代码健壮性。

示例代码整合

下面给出一个综合示例,根据对象大小自动选择拷贝方式,并在异常时捕获错误:

js 复制代码
function efficientDeepCopy(obj) {
  try {
    if (!obj) return {};

    // 对于小型对象,使用 JSON 方法可能更快
    if (JSON.stringify(obj).length < 10000) {
      return JSON.parse(JSON.stringify(obj));
    }
    // 使用结构化克隆(如果可用)
    if (typeof structuredClone === 'function') {
      return structuredClone(obj);
    }

    // 自定义深拷贝函数,处理常见数据类型
    const deepCopy = (item) => {
      if (item === null || typeof item !== 'object') {
        return item;
      }
      if (Array.isArray(item)) {
        return item.map(deepCopy);
      }
      const result = {};
      for (const key in item) {
        if (Object.prototype.hasOwnProperty.call(item, key)) {
          result[key] = deepCopy(item[key]);
        }
      }
      return result;
    };

    return deepCopy(obj);
  } catch (error) {
    console.error('efficientDeepCopy error:', error);
    return {};
  }
}

在这个示例中,根据对象序列化后的长度来判断是否使用 JSON 方法;如果对象较大且环境支持 structuredClone,则优先使用它;否则回退到自定义深拷贝函数。

总结

  • JSON 方法:适用于简单数据,性能好,但局限性明显。
  • structuredClone:适用于复杂和大型对象,兼容性需要关注,但总体性能较高。
  • 自定义深拷贝:灵活但复杂,一般作为最后的备用方案。

通过对比,可以根据项目的具体需求和对象的特点,选择最合适的深拷贝方式,从而在性能与功能之间取得平衡。

相关推荐
李少兄8 分钟前
Java 基本数据类型 vs 包装类(引用数据类型)
java·开发语言·python
九转苍翎10 分钟前
Java EE——线程状态
android·java·java-ee
佳腾_11 分钟前
Docker技术系列文章,第十篇——Docker 集群与编排(以 Kubernetes 为例)
java·运维·docker·云原生·容器·kubernetes
还是鼠鼠12 分钟前
Node.js 如何发布一个 NPM 包——详细教程
前端·vscode·npm·node.js
kiro_102312 分钟前
【ESP32S3】esp32获取串口数据并通过http上传到前端
前端·网络协议·http
昨天今天明天好多天12 分钟前
【Java】JVM
java·jvm
鎏年_2 小时前
Vue2项目打包后,某些图片被转换为base64导致无法显示
前端·javascript·vue.js
康6203 小时前
vue2中引入elementui
前端·javascript·elementui
听风说雨的人儿3 小时前
ElementUI时间选择、日期选择
前端·javascript·elementui
xiao--xin4 小时前
使用ProcessBuilder执行FFmpeg命令,进程一直处于阻塞状态,一直没有返回执行结果
java·笔记·ffmpeg·个人开发·缓冲区·进程阻塞