JS structuredClone实现数组深拷贝的方法与兼容性

作者:小括号

时间:2026-11-03

浏览:242

详细介绍使用 JavaScript 原生 structuredClone API 实现复杂数组深拷贝的方法、底层机制、与传统浅拷贝及 JSON 序列化的关键差异、边界限制与全平台兼容性方案。

文章目录
JS structuredClone实现数组深拷贝的方法与兼容性
图 0:JavaScript 原生 structuredClone() 数组深拷贝机制全景
在现代 JavaScript 中深拷贝数组,原生全局方法 structuredClone() 是官方标准的解决方案。它基于 HTML 规范中的结构化克隆算法(Structured Clone Algorithm),能递归遍历多维嵌套数组并为每个引用对象分配独立的堆内存,彻底规避了展开运算符浅拷贝的共享引用隐患,同时也解决了 JSON.parse(JSON.stringify()) 丢失特定类型和遇到循环引用直接抛错的硬伤。

一、使用 structuredClone 进行数组深拷贝的具体方法

在支持该 API 的环境中,structuredClone 作为一个挂载在全局作用域(浏览器 window、Node.js global 以及 Web Worker self)上的原生函数,其基本调用格式非常直接:

// 基础调用语法
const clonedArray = structuredClone(originalArray);

为了理解为什么必须使用深拷贝,我们先来看日常开发中极易产生隐蔽 Bug 的“浅拷贝”行为。很多开发者习惯使用展开运算符 [...arr] 或 arr.slice() 来复制数组。但这些操作只复制了数组的最外层容器;如果数组元素中包含对象、子数组或其他引用数据类型,复制的只是其在内存中的引用指针。

// 场景对比:浅拷贝 vs structuredClone 深拷贝
const originalUsers = [
  { id: 1, name: "Alice", tags: ["frontend", "react"] },
  { id: 2, name: "Bob", tags: ["backend", "node"] }
];

// 1. 使用浅拷贝 (展开运算符)
const shallowUsers = [...originalUsers];

// 修改浅拷贝内部的嵌套对象
shallowUsers[0].name = "Alicia";
shallowUsers[0].tags.push("vue");

// 观察原数组:原数组被同步篡改!
console.log(originalUsers[0].name); // 输出: "Alicia" (数据污染)
console.log(originalUsers[0].tags); // 输出: ["frontend", "react", "vue"]

// 2. 使用 structuredClone 进行深拷贝
const deepUsers = structuredClone(originalUsers);

// 修改深拷贝数据
deepUsers[1].name = "Robert";
deepUsers[1].tags.push("rust");

// 观察原数组:原数组保持纯净,毫发无损
console.log(originalUsers[1].name); // 输出: "Bob" (完全隔离)
console.log(originalUsers[1].tags); // 输出: ["backend", "node"]
console.log(deepUsers[1].name);     // 输出: "Robert"

深拷贝的执行过程不仅是新建一层外层数组,而是深度遍历原始数组中的每个索引项,当遇到普通对象、集合、数组时,在新的内存空间重新开辟存储块并赋值。

JS structuredClone实现数组深拷贝的方法与兼容性
图 1:浅拷贝与 structuredClone 深拷贝底层执行步骤及内存隔离对比

二、关键场景对比:structuredClone 与传统 JSON 方案的差异

在 structuredClone 出现之前,绝大多数教程和项目最普遍采用的“无依赖深拷贝”手段是 JSON.parse(JSON.stringify(arr))。这种方式利用 JSON 序列化与反序列化切断内存引用。然而,这种技巧并非真正的结构克隆,而是基于文本协议转换的折中方案。一旦数组中含有现代开发常用的复杂数据结构,就会出现严重的数据丢失甚至运行时崩溃。

1. 循环引用数组处理对比

在图形图谱、树形联动或具有父子反向引用的数组结构中,节点常常直接或间接包含对自身的引用。

// 构造一个包含循环引用的多维数组
const circularArray = [1, 2];
circularArray.push(circularArray); // 索引 2 引用自身

// 尝试使用 JSON 序列化
try {
  JSON.parse(JSON.stringify(circularArray));
} catch (err) {
  console.error(err.message);
  // 输出: Converting circular structure to JSON
  // 直接抛出 TypeError 异常导致程序中断
}

// 使用 structuredClone 进行深拷贝
const safeClonedArray = structuredClone(circularArray);
console.log(safeClonedArray[0]); // 输出: 1
console.log(safeClonedArray[2] === safeClonedArray); // 输出: true!完美复原循环拓扑
console.log(safeClonedArray[2] === circularArray);   // 输出: false!具有全新的内存隔离

2. 特殊内置对象的保留对比

在现代数据处理中,数组往往承载着 Date、RegExp、Map、Set 或 Uint8Array 等数据对象:

const complexData = [
  new Date("2026-10-31T08:00:00Z"),
  /^[a-z]+$/gi,
  new Set(["admin", "editor"]),
  new Map([["status", 200]]),
  undefined
];

// JSON 方案的问题
const jsonCloned = JSON.parse(JSON.stringify(complexData));
console.log(typeof jsonCloned[0]); // string! Date 对象被降级为普通字符串,原型方法丢失
console.log(jsonCloned[1]);        // {}! 正则表达式变成空对象
console.log(jsonCloned[2]);        // {}! Set 变成空对象,所有集合元素丢失
console.log(jsonCloned[3]);        // {}! Map 变成空对象,键值全部蒸发
console.log(jsonCloned[4]);        // null! undefined 被强制转换成了 null

// structuredClone 的表现
const nativeCloned = structuredClone(complexData);
console.log(nativeCloned[0] instanceof Date);   // true,仍可调用 .getTime() 等全部原生方法
console.log(nativeCloned[1] instanceof RegExp); // true,保留正则规则与 flags: "gi"
console.log(nativeCloned[2] instanceof Set);    // true,含有 "admin" 和 "editor"
console.log(nativeCloned[3] instanceof Map);    // true,保留 status => 200 映射
console.log(nativeCloned[4]);                   // undefined,忠实保留原始值

3. 稀疏数组(Sparse Array)空位的保留

若数组中存在未赋值的空槽(Holes),例如通过 new Array(3) 或 [1, , 3] 声明的稀疏数组:

  • JSON.stringify 会将空槽全部强行替换为 null;
  • structuredClone 则会严格保持其稀疏性质,克隆后的数组仍然包含空槽,不会凭空产生 null 填充。
JS structuredClone实现数组深拷贝的方法与兼容性
图 2:常用数组拷贝方案在复杂数据类型支持维度的对比矩阵

三、使用边界与 DataCloneError 异常处理

虽然 structuredClone 支持绝大多数结构化数据,但它严格遵循 HTML 规范定义的安全与可传输标准。如果试图拷贝以下类型的数据,引擎将立即抛出 DOMException: DataCloneError 异常:

重要边界提醒:当数组内包含以下内容时,structuredClone 将无法执行并抛出 DataCloneError:
  • 函数与方法:普通的 function、箭头函数、类方法等;
  • DOM 节点:如 HTMLElement、Document 节点;
  • 属性描述符与 getter/setter:只复制当前属性取值,read-only 或 enumerable: false 的配置会被擦除;
  • 原型链(Prototype):克隆由自定义类通过 new MyClass() 实例化的对象时,克隆得到的新对象会丢失原有类的原型链,退化为普通纯 Object。

针对此类边界,在面向用户输入或外部动态输入数组时,推荐采用防御性编程范式进行处理:

// 带有防御性回退的数组深拷贝封装示例
function safeDeepCloneArray(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError("参数必须为数组");
  }

  try {
    // 优先尝试原生 structuredClone,性能好且全面支持 Map/Set/Date/循环引用
    return structuredClone(arr);
  } catch (error) {
    if (error.name === "DataCloneError") {
      console.warn("数组中包含不可克隆对象(如函数或DOM节点),退回手动浅拷贝或定制过滤方案");
      // 场景化降级策略:例如过滤函数属性,或按需手动浅拷贝第一层
      return arr.map(item => (typeof item === "object" && item !== null ? { ...item } : item));
    }
    throw error;
  }
}
JS structuredClone实现数组深拷贝的方法与兼容性
图 3:数组拷贝选型路径与边界异常判断决策树

四、运行环境兼容性与降级方案

structuredClone 并非一个只属于浏览器的 DOM 扩展,它已被正式定义在 HTML 规范的顶层全局命名空间,并被 W3C 与各大主流引擎厂商收录在 Baseline 2022(全平台基线标准) 中。

1. 现代浏览器支持版本

  • Google Chrome:98 及以上版本(2022 年 2 月发布)完全支持;
  • Microsoft Edge:98 及以上版本同步支持;
  • Mozilla Firefox:94 及以上版本(2021 年 11 月发布)率先实现;
  • Apple Safari:15.4 及以上版本(macOS Monterey 及 iOS 15.4,2022 年 3 月)正式支持。

根据 Can I Use 统计,目前全球已有超过 96% 的活跃终端浏览器原生支持 structuredClone。在现代前端工程中,面向大众用户的项目通常可以直接放心使用。

2. 服务端与命令行运行时支持

  • Node.js:在 17.0.0 中首次引入实验支持,从 Node.js 18.0.0 LTS 开始作为稳定全局函数提供,无需任何额外 require 或 import;
  • Deno:在 v1.14 开始原生提供;
  • Bun:自 v0.1.0 起全版本开箱即用支持。

3. 兼容旧环境的降级方案(Polyfill)

若业务场景需要继续支持较旧系统(例如 iOS 14、旧款内嵌 WebView 或 Node.js 16 以下),可以在工程入口引入成熟的 Polyfill 垫片。

// 方案 1: 安装轻量级 polyfill 包 (约 3.5KB,规范还原度高)
// npm install @ungap/structured-clone

import structuredClonePolyfill from '@ungap/structured-clone';

// 环境检测与平滑回退
const safeClone = typeof structuredClone === "function" 
  ? structuredClone 
  : structuredClonePolyfill;

const myArray = [{ id: 1 }, new Set([1, 2, 3])];
const result = safeClone(myArray);

如果项目已经使用了 core-js 作为垫片集,也可以直接引入对应的子模块:

// 方案 2: 使用 core-js
import "core-js/actual/structured-clone";
JS structuredClone实现数组深拷贝的方法与兼容性
图 4:structuredClone 主流浏览器与现代 JS 运行时兼容性全景图

五、总结与工程选型建议

在实现 JavaScript 数组深拷贝时,structuredClone 已经成为当之无愧的首选方案。在实际工程落地中,可以遵循以下三条建议:

  1. 简单扁平数组优先用浅拷贝:对于只包含数字、字符串等基本类型的纯一维数组,直接使用 [...arr] 或 arr.slice(),执行开销最低,性能表现最佳。
  2. 复杂结构数组默认选用 structuredClone:对于嵌套子对象、二维数组、带有时间/正则/集合或存在循环引用的复杂数组,统一采用 structuredClone(arr),避免自行编写递归函数带来的边界漏洞或 JSON 序列化的数据腐败。
  3. 警惕函数与 DOM 节点传递:在传递包含业务逻辑方法、观察者回调或页面元素的数组时,务必提前做好数据与视图/行为的解耦,防止抛出 DataCloneError。对于必须面向旧版本客户端的项目,提前配置 @ungap/structured-clone 垫片即可实现无缝降级。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章