【笔记】js 内置数据深拷贝 API-structuredClone
Feb 4, 2024笔记前端jsjs 内置数据深拷贝 API-structuredClone()
介绍
全局的 structuredClone()
方法使用结构化克隆算法
(见下文)将给定的值进行深拷贝。
该方法还支持把原始值中的可转移对象转移到新对象,而不是把属性引用拷贝过去,支持循环引用。 可转移对象与原始对象分离并附加到新对象;它们不可以在原始对象中访问被访问到。
语法
1 | structuredClone(value) |
其中参数:
value
:被克隆的对象。可以是任何结构化克隆支持的类型。transfer
: 可选。是一个可转移对象的数组,里面的值
并没有被克隆,而是被转移到被拷贝对象上。
返回值:
- 返回值是原始值的深拷贝。
示例:
1 | // Create an object with a value and a circular reference to itself. |
structuredClone
支持拷贝 js 的各种内置的类型,比如:Date
,Set
,Map
,Error
,RegExp
,ArrayBuffer
,Blob
,File
,ImageData
等
1 | const structured = [{ a: 42 }]; |
API 规范
https://html.spec.whatwg.org/multipage/structured-data.html#transferable-objects
结构化克隆算法(Structured clone algorithm)
结构化克隆算法是 ECMAScript 2019 中引入的。 它使用了一种新的算法,该算法可以复制任意类型的值,包括循环引用。结构化克隆算法是用于复制复杂 js 对象的算法。
结构化克隆所不能做到的
Function
对象是不能被结构化克隆算法复制的;如果你尝试这样子去做,这会导致抛出DATA_CLONE_ERR
的异常。- 企图去克隆 DOM 节点同样会抛出
DATA_CLONE_ERR
异常。 - 对象的某些特定参数也不会被保留
RegExp
对象的lastIndex
字段不会被保留- 属性描述符,
setters
以及getters
(以及其他类似元数据的功能)同样不会被复制。例如,如果一个对象用属性描述符标记为read-only
,它将会被复制为read-write
,因为这是默认的情况下。 - 原形链上的属性也不会被追踪以及复制。
支持的类型
JavaScript 类型:Array
、ArrayBuffer
、Boolean
、DataView
、Date
、Error
类型(仅限部分 Error 类型)。Map
、Object
对象:仅限简单对象(如使用对象字面量创建的)。除 symbol
以外的基本类型。RegExp
:lastIndex
字段不会被保留。Set
。String
。TypedArray
其中 Error
类型仅支持以下:Error
、EvalError
、RangeError
、ReferenceError
、SyntaxError
、TypeError
、URIError
(或其他会被设置为 Error
的)。
浏览器必须序列化 name
和 message
字段,其他有意义的字段则可能会序列化,如 stack
、cause
等。
兼容性
注意:Nodejs 是 v17.0
起支持。
Structured Clone 算法其实早已在部分浏览器场景中出现。例如,每当调用
postMessage
将消息发送到另一个窗口或WebWorker
时、或者使用IndexedDB
中存储 JS 值时,都会使用到这个算法。
Polyfill
实现大致可以概括为:
1 | // 以下是core-js对于structuredClone实现的伪代码 |
主要利用深度优先递归遍历的方式,实现对对象及其属性的克隆。并使用 Map 记录映射关系,针对不同类型做定制化处理。
主要功能点如下:
- 检查浏览器原生的
structuredClone
实现是否正确。包括: 克隆Set
、错误对象、新错误对象语义等的支持。 - 如果原生实现有问题,则使用自己的 polyfill 实现代替。
- polyfill 通过递归遍历对象,根据类型创建副本。支持各种基础类型、
ArrayBuffer
、错误对象等的克隆。 - 支持
transferables
参数,用于转移对象的所有权,避免拷贝。调用原生的结构化克隆方法实现。 - 处理各种边界案例:重复
transferables
、不可 transfer 的类型、分离ArrayBuffers
等。 - 最终返回一个深层次拷贝的 clone 对象。
所以这是一份功能完整的 structuredClone polyfill
。它考虑了规范要求、不同浏览器的实现问题、边界案例等,提供了强大的备选实现。
core-js 中的
structuredClone
的 polyfill ,还没有解决 ArrayBuffer 实例和许多平台类型无法在大多数引擎中传输的问题,所以 当需求兼容较低版本浏览器是因尽量避免使用structuredClone(value, { transfer })
的第二个参数。
另外还有份单独的 polyfill 也可以参考:https://github.com/ungap/structured-clone
对比其他深拷贝方法
日常开发中,我们经常使用 JSON.parse(JSON.stringify(obj))
或者 lodash 中的_.cloneDeep()
来实现深拷贝。
在不考虑大批量数据处理的性能情况下,单从日常使用以下做个简单的比较:
API | JSON.parse(JSON.stringify()) |
_.cloneDeep() |
structuredClone() |
---|---|---|---|
优点 | 全局 api, 简单易用 | 支持复杂数据类型的拷贝 | 全局 api,简单易用;支持复杂数据类型的拷贝 |
缺点 | 只能处理基本对象、数组和原始类型。任何其他类型都会以难以预测的方式处理。例如,Date 被转换为字符串 | 这个函数会占用17.4 kb (5.3 kb gzip) |
可能有兼容性问题(低端浏览器、低版本 nodejs) |
总结
综合来看,structuredClone()
可以作为一个深拷贝的优选方案,在使用中需要考虑一下兼容性(polyfill 的适配)情况。
参考资料
- https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
- https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.structured-clone.js
- https://surma.dev/things/deep-copy/index.html
- https://html.spec.whatwg.org/multipage/structured-data.html#transferable-objects
Author
My name is Micheal Wayne and this is my blog.
I am a front-end software engineer.
Contact: michealwayne@163.com