知道吗,在JavaScript中现在有一种原生的方法来做对象的深层复制?
没错,这个 structuredClone
函数内置在JavaScript运行时中:
const calendarEvent = {
title: "Builder.io Conf",
date: new Date(123),
attendees: ["Steve"]
}
const copied = structuredClone(calendarEvent)
所有的结果都如预期的那样:
copied.attendees // ["Steve"]
copied.date // Date: Wed Dec 31 1969 16:00:00
cocalendarEvent.attendees === copied.attendees // false
是的, structuredClone
不仅可以做以上的事情,而且还可以:
- 克隆无限嵌套的对象和数组
- 克隆循环引用
- 克隆各种各样的JavaScript类型,如
Date
、Set
、Map
、Error
、RegExp
、ArrayBuffer
、Blob
、File
、ImageData
等 - 转让任何可转移对象
例如:
const kitchenSink = {
set: new Set([1, 3, 3]),
map: new Map([[1, 2]]),
regex: /foo/,
deep: { array: [ new File(someBlobData, 'file.txt') ] },
error: new Error('Hello!')
}
kitchenSink.circular = kitchenSink
const clonedSink = structuredClone(kitchenSink)
值得注意的是,我们谈论的是一个深度副本。如果你只需要做一个浅拷贝,也就是不复制嵌套对象或数组的拷贝,那么我们可以只做一个对象扩展:
const simpleEvent = {
title: "Builder.io Conf",
}
const shallowCopy = {...calendarEvent}
如果你喜欢的话,也可以是这个
const shallowCopy = Object.assign({}, simpleEvent)
const shallowCopy = Object.create(simpleEvent)
但是一旦我们有了嵌套项,我们就会遇到麻烦:
const calendarEvent = {
title: "Builder.io Conf",
date: new Date(123),
attendees: ["Steve"]
}
const shallowCopy = {...calendarEvent}
shallowCopy.attendees.push("Bob")
shallowCopy.date.setTime(456)
正如你所看到的,我们没有做这个对象的完整副本。
为什么不是 JSON.parse(JSON.stringify(x))
?
举个例子:
const calendarEvent = {
title: "Builder.io Conf",
date: new Date(123),
attendees: ["Steve"]
}
const problematicCopy = JSON.parse(JSON.stringify(calendarEvent))
如果我们打印 problematicCopy
,我们将得到:
{
title: "Builder.io Conf",
date: "1970-01-01T00:00:00.123Z"
attendees: ["Steve"]
}
这不是我们想要的! date
应该是 Date
对象,而不是字符串。
这是因为 JSON.stringify
只能处理基本的对象、数组和原语。任何其他类型都难以预测。例如,日期转换为字符串。Set
只是转换为 {}
。
JSON.stringify
甚至完全忽略了某些东西,比如 undefined
或函数。
例如,如果我们复制 kitchenSink
:
const kitchenSink = {
set: new Set([1, 3, 3]),
map: new Map([[1, 2]]),
regex: /foo/,
deep: { array: [ new File(someBlobData, 'file.txt') ] },
error: new Error('Hello!')
}
const veryProblematicCopy = JSON.parse(JSON.stringify(kitchenSink))
我们将得到:
{
"set": {},
"map": {},
"regex": {},
"deep": {
"array": [
{}
]
},
"error": {},
}
_.cloneDeep
?
到目前为止,Lodash的 cloneDeep
函数一直是这个问题的一个非常常见的解决方案。
事实上,这是按照预期工作的:
import cloneDeep from 'lodash/cloneDeep'
const calendarEvent = {
title: "Builder.io Conf",
date: new Date(123),
attendees: ["Steve"]
}
const clonedEvent = cloneDeep(calendarEvent)
structuredClone
什么不能克隆
不能克隆函数
会抛出一个 DataCloneError
异常:
// 🚩 Error!
structuredClone({ fn: () => { } })
DOM节点
也会抛出 DataCloneError
异常:
// 🚩 Error!
structuredClone({ el: document.body })
属性描述符、setter和getter
以及类似元数据的功能不会被克隆。
例如,使用getter,结果值被克隆,但getter函数本身(或任何其他属性元数据)不会被克隆:
structuredClone({ get foo() { return 'bar' } })
// Becomes: { foo: 'bar' }
对象原型
原型链不会被遍历或复制。因此,如果您克隆 MyClass
的实例,则克隆的对象将不再是该类的实例(但该类的所有有效属性将被克隆)。
class MyClass {
foo = 'bar'
myMethod() { /* ... */ }
}
const myClass = new MyClass()
const cloned = structuredClone(myClass)
// Becomes: { foo: 'bar' }
cloned instanceof myClass // false
支持类型的完整列表
更简单地说,任何不在下面列表中的内容都不能被克隆:
js 内置
Array
、 ArrayBuffer
、 Boolean
、 DataView
、 Date
、 Error
类型(下面特别列出的)、 Map
、 Object
,但仅限于普通对象(例如,从对象字面量),原始类型,除了 symbol
(又名 number
, string
, null
, undefined
, boolean
, BigInt
), RegExp
, Set
, TypedArray
错误类型
Error
, EvalError
, RangeError
, ReferenceError
, SyntaxError
, TypeError
, URIError
WEB/API 类型
AudioData
, Blob
, CryptoKey
, DOMException
, DOMMatrix
, DOMMatrixReadOnly
, DOMPoint
, DomQuad
, DomRect
, File
, FileList
, FileSystemDirectoryHandle
, FileSystemFileHandle
, FileSystemHandle
, ImageBitmap
, ImageData
, RTCCertificate
, VideoFrame
原文: www.builder.io/blog/struct…
原文链接:https://juejin.cn/post/7348322659123757108 作者:关山月