- Published on
JavaScript中深拷贝对象的最有效方法
- Authors
- Name
在JavaScript编程中,经常需要对对象进行深拷贝操作。深拷贝指的是完整复制对象及其嵌套对象,确保无副作用地修改克隆对象。本文将介绍几种在JavaScript中实现深拷贝的方法,并特别强调structuredClone
函数的使用及其在不同环境中的支持情况。
使用结构化克隆(Structured Cloning)
2022年更新: structuredClone
全局函数已经在Firefox 94、Node 17和Deno 1.14中可用。
HTML标准中包含了内部结构化克隆/序列化算法,可以创建对象的深度克隆。这种方法扩展了JSON的支持范围,不仅支持JSON支持的类型,还支持Date
、RegExp
、Map
、Set
、Blob
、FileList
、ImageData
、稀疏数组、TypedArray
等类型,并且未来可能支持更多类型。而且,它能在克隆数据中保留引用,从而支持循环和递归结构,这在使用JSON时会出错。
在Node.js中的支持:
structuredClone
全局函数已经在Node 17.0中可以使用:
const clone = structuredClone(original)
对于之前的版本,Node.js中的v8
模块(从Node 11开始)直接暴露了结构化序列化API,但此功能仍被标记为“实验性”并可能会在未来版本中更改或移除。如果您正在使用兼容版本,克隆对象的方法如下:
const v8 = require('v8')
const structuredClone = (obj) => {
return v8.deserialize(v8.serialize(obj))
}
浏览器中的直接支持:已在Firefox 94中实现
structuredClone
全局函数将会在所有主流浏览器中提供支持(在whatwg/html#793 on GitHub中进行了讨论)。其用法如下:
const clone = structuredClone(original)
在这一功能发布之前,浏览器的结构化克隆实现只能间接使用。
异步解决方案:可用解决方案
利用已有API创建结构化克隆的低开销方法是通过一个MessageChannel的两端进行数据传递。另一端会发出一个message
事件,其中包含克隆数据。然而,监听这些事件必须是异步的,同步替代方案的实用性较差。
class StructuredCloner {
constructor() {
this.pendingClones_ = new Map()
this.nextKey_ = 0
const channel = new MessageChannel()
this.inPort_ = channel.port1
this.outPort_ = channel.port2
this.outPort_.onmessage = ({ data: { key, value } }) => {
const resolve = this.pendingClones_.get(key)
resolve(value)
this.pendingClones_.delete(key)
}
this.outPort_.start()
}
cloneAsync(value) {
return new Promise((resolve) => {
const key = this.nextKey_++
this.pendingClones_.set(key, resolve)
this.inPort_.postMessage({ key, value })
})
}
}
const structuredCloneAsync = (window.structuredCloneAsync =
StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner()))
示例用法:
const main = async () => {
const original = { date: new Date(), number: Math.random() }
original.self = original
const clone = await structuredCloneAsync(original)
// 它们是不同的对象:
console.assert(original !== clone)
console.assert(original.date !== clone.date)
// 它们是循环引用的:
console.assert(original.self === original)
console.assert(clone.self === clone)
// 它们包含等效的值:
console.assert(original.number === clone.number)
console.assert(Number(original.date) === Number(clone.date))
console.log('断言完成。')
}
main()
同步解决方案:糟糕的选择
没有好的方法可以同步创建结构化克隆。这儿有几种不实用的黑客技巧。
history.pushState()
和history.replaceState()
都可以创建其第一个参数的结构化克隆,并将该值赋给history.state
。可以这样创建一个对象的结构化克隆:
const structuredClone = (obj) => {
const oldState = history.state
history.replaceState(obj, null)
const clonedObj = history.state
history.replaceState(oldState, null)
return clonedObj
}
示例用法:
尽管这是同步的,但此方法可能非常慢。它会产生与操作浏览器历史记录相关的所有开销。重复调用此方法可能导致Chrome暂时无响应。
Notification
构造函数可以创建其关联数据的结构化克隆。它也会尝试向用户展示浏览器通知,但如果您没有请求通知权限,则该尝试会静默失败。假如您出于其他目的已经具有权限,我们会立即关闭创建的通知。
const structuredClone = (obj) => {
const n = new Notification('', { data: obj, silent: true })
n.onshow = n.close.bind(n)
return n.data
}
以上就是几种在JavaScript中进行深拷贝的方法。structuredClone
全局函数无疑是最便捷和高效的选择,其在未来的广泛支持将使开发者更轻松地实现对象的深度克隆。