news 2026/9/12 9:18:04

core-js 中的 structuredClone:HTML 标准结构化克隆的 Polyfill 原理与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
core-js 中的 structuredClone:HTML 标准结构化克隆的 Polyfill 原理与实战

core-js 中的 structuredClone:HTML 标准结构化克隆的 Polyfill 原理与实战

【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js

structuredClone是 WHATWG HTML 标准提供的全局深拷贝 API,能够递归克隆绝大多数 JavaScript 内置类型与平台对象,并天然支持循环引用、共享引用与ArrayBuffer转移。本文以 core-js 对web.structured-clone模块的完整实现为主线,讲解该 polyfill 的入口点、可克隆类型清单、源码级克隆算法,以及transfer选项的引擎边界与替代方案,帮助你在旧浏览器、旧版 Node.js 等环境中安全落地深拷贝逻辑。

从 JSON 深拷贝到结构化克隆

structuredClone出现之前,JavaScript 社区做"深拷贝"最常用的手段是JSON.parse(JSON.stringify(obj)),但它存在大量硬伤:

  • 遇到undefined、函数、Symbol属性时会被静默丢弃;
  • DateRegExpMapSetArrayBufferTypedArray等类型会被错误地序列化成普通对象或空对象;
  • 循环引用直接抛出TypeError: Converting circular structure to JSON
  • 无法保留对象的原型与共享引用关系。

而 HTML 标准的structuredClone正是为补足这些能力而设计的:它实现了 HTML 标准中的结构化克隆算法 一节的原始说明。

模块与入口点

structuredClone的 polyfill 实现位于 modules/web.structured-clone.js,其挂载方式是向全局对象注入一个可枚举structuredClone方法:

// https://html.spec.whatwg.org/multipage/structured-data.html#dom-structuredclone $({ global: true, enumerable: true, sham: !PROPER_STRUCTURED_CLONE_TRANSFER, forced: FORCED_REPLACEMENT }, { structuredClone: function structuredClone(value /* , { transfer } */) { /* ... */ } });

forced: FORCED_REPLACEMENT意味着当检测到引擎自带的structuredClone实现存在语义缺陷时会强制替换为 core-js 版本(详见后文"引擎差异"小节)。sham: true表明在无法实现真正的 transferable 语义时,该 polyfill 被标记为"sham"(不完整的替代实现)。

按文档说明,structuredClone对应唯一的模块名web.structured-clone,其标准入口点为:

core-js(-pure)/stable|actual|full/structured-clone

即四个命名空间(stableactualfull,以及纯版本core-js-pure)下都提供了同名入口文件,仓库中对应的实际文件为:

  • stable/structured-clone.js:仅稳定 ES 特性 + Web 标准,会额外预载DOMException构造器相关模块;
  • actual/structured-clone.js:基于stable,另含 stage 3 提案;
  • full/structured-clone.js:基于actual,另含早期提案;
  • web/structured-clone.js:仅包含 Web 标准类别的最小依赖集合。

具体用法(完整入口点体系见 docs/web/docs/usage.md):

// 全局版本:直接注入全局 structuredClone import 'core-js/stable/structured-clone'; // 或一次性引入全部稳定特性 import 'core-js/stable'; // 纯版本:不污染全局命名空间,返回函数 import structuredClone from 'core-js-pure/stable/structured-clone'; // 也可以直接按需使用 import structuredClone from 'core-js-pure/actual/structured-clone';

由于structuredClone是 Web 标准方法而非 ES 方法,在core-js-pure纯版本中不会挂载到全局对象上,而是作为导出的函数使用,避免任何命名空间污染。

函数签名与参数说明

官方文档给出的类型签名为:

function structuredClone(value: Serializable, { transfer?: Sequence<Transferable> }): any;
参数类型说明
valueSerializable要深拷贝的源值。原始类型直接原样返回;可序列化对象递归克隆
options.transfer(可选)Sequence<Transferable>可转移对象列表(如ArrayBufferMessagePortImageBitmap等)。转移成功后原对象会被 detach(剥离/清空),数据所有权移交克隆体

从源码看,第二个参数的处理非常严谨(modules/web.structured-clone.js):

var options = validateArgumentsLength(arguments.length, 1) > 1 && !isNullOrUndefined(arguments[1]) ? anObject(arguments[1]) : undefined; var transfer = options ? options.transfer : undefined;
  • 少于 1 个参数时直接抛错(测试断言structuredClone()必须抛出);
  • 第二参数为nullundefined时视作未传,正常运行(structuredClone(1, null)返回1);
  • 传入了对象但没有transfer属性时,只做普通克隆,不做转移。

可克隆类型全览

structuredClone能正确处理以下类型(对应文档示例,均可直接运行):

structuredClone(42); // => 42 structuredClone({ x: 42 }); // => { x: 42 } structuredClone([1, 2, 3]); // => [1, 2, 3] structuredClone(new Set([1, 2, 3])); // => Set{ 1, 2, 3 } structuredClone(new Map([['a', 1], ['b', 2]])); // => Map{ a: 1, b: 2 } structuredClone(new Int8Array([1, 2, 3])); // => new Int8Array([1, 2, 3]) structuredClone(new AggregateError([1, 2, 3], 'message')); // => new AggregateError([1, 2, 3], 'message') structuredClone(new TypeError('message', { cause: 42 })); // => new TypeError('message', { cause: 42 }) structuredClone(new DOMException('message', 'DataCloneError')); // => new DOMException('message', 'DataCloneError') structuredClone(document.getElementById('myfileinput')); // => new FileList structuredClone(new Date('1970-01-01')); // => Date "Thu Jan 01 1970 00:00:00..." structuredClone(new Blob(['test'])); // => new Blob(['test']) structuredClone(new ImageData(8, 8)); // => new ImageData(8, 8) // etc.

不可序列化类型(函数、Symbol、全局对象、EventMessagePort等)会抛出DataCloneError

structuredClone(new WeakMap()); // => DataCloneError on non-serializable types

循环引用与共享引用

这是structuredClone相对 JSON 方案的杀手级能力——克隆结果保留引用结构而非值快照:

const structured = [{ a: 42 }]; const sclone = structuredClone(structured); console.log(sclone); // => [{ a: 42 }] console.log(structured !== sclone); // => true(顶层引用不同) console.log(structured[0] !== sclone[0]); // => true(嵌套对象也完全独立) const circular = {}; circular.circular = circular; const cclone = structuredClone(circular); console.log(cclone.circular === cclone); // => true(循环引用被正确保留,指向克隆体自身)

实现层面,这个能力来自克隆算法内部的Map记忆表(见下文源码分析),structuredCloneInternal每克隆一个对象都会登记到map中,再次遇到同一引用时直接返回已克隆对象,从而同时解决循环引用与共享引用去重两个问题。在 tests/unit-global/web.structured-clone.js 中专门验证了这一语义:{ a: shared, b: shared }克隆后multiClone.a === multiClone.b,证明共享引用没有产生两份拷贝。

源码级实现原理

polyfill 的核心是structuredCloneInternal(value, map)这个递归克隆函数(modules/web.structured-clone.js),其执行流程可以概括为:

  1. 原始值短路Symbol直接抛Uncloneable type: Symbol;非对象值(!isObject(value))原样返回;
  2. 记忆表查重:如果map中已有该引用,直接返回已有克隆,实现循环引用与共享引用保真;
  3. classof分派:使用 core-js 内部的classof工具获取对象的内置标签(如ArrayMapSetRegExpErrorDOMExceptionArrayBuffer、各类 TypedArray、DateBlobFileImageData、几何类型等),为每类类型走专属克隆分支;
  4. 登记克隆体:将value -> cloned写入map,随后再递归填充内容(属性、Map 键值、Set 元素、错误 message/cause/stack 等)。

几个值得注意的实现细节:

  • RegExp:不直接依赖引擎的 RegExp 构造克隆,而是用value.sourcegetRegExpFlags(value)重新构造,以规避 Safari 14.1 无法克隆部分 flags 的 bug;
  • ArrayBuffer 家族cloneBuffer优先调用value.slice(0),若缓冲区可伸缩(resizable)则改用new ArrayBuffer(length, { maxByteLength })并逐字节复制;SharedArrayBuffer由于共享内存语义无法 polyfill,在无原生支持时直接返回原对象(源码注释明确说明"we can't polyfill it, so return the original");
  • Error 类:按name分派到AggregateError、内置错误、WebAssembly.CompileError/LinkError/RuntimeError等分支,并额外复制messagecauseerrorssuppressed与可安装的stack属性;
  • FileList:优先通过DataTransferitems.add()逐个重建文件列表,DataTransfer不可用时回退到受限的原生克隆;
  • 兜底策略:对于AudioDataVideoFrame等平台类型,要求其具备clone()方法;对于CryptoKeyImageBitmapWebAssembly.Module等无法同步克隆的类型,直接抛出DataCloneError("cannot be properly polyfilled in this engine")。

transfer 选项:能力、边界与官方警告

transfer选项允许在克隆的同时转移可转移对象的所有权——原对象被 detach(如ArrayBufferbyteLength变为 0),克隆体接管底层内存,从而避免大块数据的复制开销:

const buffer = new ArrayBuffer(8); const view = new Uint8Array(buffer); view.set([1, 2, 3, 4]); const clone = structuredClone(buffer, { transfer: [buffer] }); // clone 为独立的 8 字节缓冲区 // 原 buffer.byteLength === 0(已被剥离)

tryToTransfer会先校验 transfer 序列中每个元素都是对象,并拒绝重复的 transferable(抛Duplicate transferable)。对于ArrayBuffer采用"先登记、后剥离"的两阶段策略:先克隆所有缓冲,再通过detachBuffers统一剥离,原因在源码注释中说明:受"克隆已转移缓冲区的视图"问题影响,必须延后剥离(对应 core-js issue #1265)。

不过,官方文档对transfer给出了明确的警告

[!WARNING]

  • 许多平台类型在大多数引擎中都无法真正 transfer(polyfill 无法模拟该行为),但.transfer选项对部分平台类型有效。推荐尽量避免使用该选项
  • 部分特定平台类型在旧引擎中无法克隆。主要是一些非常特殊的类型或非常旧的引擎,但也存在例外。例如在 Safari 14.0- 或 Firefox 83- 中无法同步克隆ImageBitmap,如需克隆特定类型,建议查看 polyfill 源码。

PROPER_STRUCTURED_CLONE_TRANSFER(internals/structured-clone-proper-transfer.js)用于探测引擎是否具备"正确的" transfer 语义:它创建一个 8 字节ArrayBuffer并尝试structuredClone(buffer, { transfer: [buffer] }),若原 buffer 未被剥离或克隆体长度不正确,则认为该引擎的 transfer 实现不合格。探测还对不同运行时做了 V8 版本门槛(Deno V8 > 92、Node V8 > 94、浏览器 V8 > 97),以规避 V8 的 ArrayBufferDetaching protector cell 失效导致的性能退化问题。

引擎差异与强制替换策略

core-js 之所以在原生已有structuredClone的引擎上仍然可能启用 polyfill,是因为历史上有大量引擎实现存在语义缺陷。源码中的检测逻辑(FORCED_REPLACEMENT)会逐一验证:

  • 错误对象克隆:早期 FF 与 Safari 无法克隆 Error(如 FF<103),FF103 克隆的.stack为空字符串,FF104 修复普通错误但DOMException仍有问题;
  • 错误引用去重:Chrome<102在克隆对象包含多处同一错误引用时返回null(V8 issue 12542);
  • 新错误克隆语义:只有 FF103+ 支持 WHATWG html/5749 的新语义(AggregateErrornameerrorscause均需正确克隆);
  • Node.js:Node 实现无法克隆DOMException(nodejs/node#41038),Node<17.2performance.mark克隆实现过于朴素,无法克隆RegExp或装箱原始值。

此外,模块还实现了一个巧妙的"备胎"方案:在完全没有原生structuredClone的引擎中,尝试用new PerformanceMark(uid, { detail: value }).detailPerformanceMark的 detail 字段"借道"取回克隆值,用于checkBasicSemantic验证后的受限克隆路径。

这些引擎差异同样体现在tests/unit-global/web.structured-clone.js中:测试用例(源自 WPT 结构化克隆测试集)覆盖了原始值、装箱原始值、DateRegExpArrayBuffer、可伸缩ArrayBuffer、全部 TypedArray、DataViewMap/Set、各类 Error、数组/对象、几何类型(DOMMatrixDOMPointDOMQuadDOMRect及其 ReadOnly 变体)、ImageDataBlobFileFileList、循环/共享引用、transfer剥离以及不可序列化类型的异常路径。

实践建议与常见问题

  • 优先用actual命名空间按需引入import 'core-js/actual/structured-clone'既能覆盖最新稳定 Web 标准,又不会带入早期不稳定提案;
  • 服务端场景:Node.js 16.0+ 才有structuredClone,且 Node<17.2的实现有缺陷——core-js 会通过FORCED_REPLACEMENT自动替换,因此低版本 Node 直接引入 core-js 即可获得符合规范的行为;
  • 深拷贝大对象:普通克隆走递归复制;只有当数据体积大、且目标对象确属可 transfer 类型(如ArrayBuffer)时才考虑transfer,并接受"部分平台类型无法 polyfill"的边界;
  • 克隆失败统一为DataCloneError:不可克隆类型(函数、SymbolWeakMap、全局对象等)一律抛出DOMException(name 为DataCloneError),可据此做统一的 try/catch 降级处理。

小结

structuredClone补全了 JavaScript 深拷贝在类型覆盖、循环引用、共享引用与内存转移四个维度的能力,而 core-js 的 web.structured-clone 模块则在其上提供了跨引擎一致的实现:既有完善的类型分派与引用保真算法,也有针对各引擎历史 bug 的探测与强制替换机制。无论你是在兼容旧浏览器,还是在为低版本 Node.js 补齐全局 API,按core-js(-pure)/stable|actual|full/structured-clone入口点引入即可获得与规范对齐的深拷贝能力。

【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 9:15:59

PowerShell自动化提取MHTML附件技术详解

1. 项目概述&#xff1a;MHTML附件提取的自动化方案 MHTML&#xff08;MIME HTML&#xff09;作为网页存档的标准格式&#xff0c;能够将网页中的文本、图片、CSS等资源打包成单一文件。但在实际工作中&#xff0c;我们经常需要从这类复合文档中提取特定附件。传统的手动解压操…

作者头像 李华
网站建设 2026/9/12 9:15:35

图结构推荐系统:异构图构建与双路径推荐实现

简介&#xff1a;本资源是一套基于图神经网络的推荐系统实现方案&#xff0c;面向算法工程师、推荐系统学习者及高校相关专业学生&#xff0c;聚焦用户画像与商品标签融合建模这一核心问题。系统依托亚马逊真实交易数据集&#xff08;含500万订单、200万商品及800万标签&#x…

作者头像 李华
网站建设 2026/9/12 9:15:11

OLAP资源隔离与调度策略:从失控到可控的实战指南

干过大数据的同学应该都有这种体会&#xff1a;白天业务线正在跑例行报表&#xff0c;几条即席分析查询冲进来&#xff0c;集群 CPU 瞬间打满&#xff0c;内存持续告警&#xff0c;紧接着是一串 Executor Lost、Container OOM 的报错。到了晚上&#xff0c;离线任务和实时宽表构…

作者头像 李华
网站建设 2026/9/12 9:14:53

SQL注入入门:SQLi-Labs Less-1实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 9:13:05

Python爬虫实战:地名志数据采集与SQLite存储

1. 项目背景与核心价值地名志这类地方志文献通常包含行政区划沿革、地名由来、地理特征等珍贵数据&#xff0c;但往往以PDF或网页形式存在&#xff0c;难以直接分析利用。去年我在做一个历史文化研究项目时&#xff0c;需要批量分析3000多个地名的时空分布特征&#xff0c;手动…

作者头像 李华