现在,JavaScript 提供了一个底层的方法来深度克隆一个对象, 他就是 structureClone
。
const obj = {
title: 'Object Title',
date: new Date(),
skills: ['php', 'javascript']
}
const copied = structuredClone(obj)
structureClone
不但可以复制对象,而且可以复制嵌套的数组,事件,时间对象。
copied.date
// Mon Feb 13 2023 23:40:53 GMT+0800 (中国标准时间)
copied.skills
// (2) ['php', 'javascript']
对的, structureClone
不但可以复制上面的内容,还可以作用于:
- 无限嵌套的对象和数组
- 循环引用
- 各种类型,包括
Date
,Set, Map, Error, RegExp, ArrayBuffer, Blob, File, ImageData 等等。
为什么不用 对象展开?
重要的是深度复制,如果只是 浅层复制(shallow copy), 不需要复制嵌套对象或者数组,我们只需要做对象展开。
const simpleObject = {
title: "Object Title",
skills: ["php"]
}
const shallowCopy = {...simpleObject}
或者
const shallowCopy = Object.assign({}, simpleObject)
const shallowCopy = Object.create(simpleObject)
但是一旦有嵌套,就会有麻烦。
复制对象的修改会改变原来的对象。
const simpleObject = {
title: "Object Title",
skills: ["php"]
}
const shallowCopy = {...simpleObject}
// undefined
shallowCopy.skills.push("JS")
// 2
shallowCopy.skills
// (2) ['php', 'JS']
simpleObject.skills
// (2) ['php', 'JS']
可以看到,我们没有完全复制原来的对象。 嵌套的数组依然是原来引用地址。
用 JSON.parse(JSON.stringify(x)) ?
对的,这是个很好的窍门,而且非常高效。但有一些缺点,structuredClone
解决了。
例如:
const calendarEvent = {
title: "Event Title",
date: new Date(),
attendees: ["Apple"]
}
// JSON.stringify 转换 `date` 成字符串
const problematicCopy = JSON.parse(JSON.stringify(calendarEvent))
// console.log
{
title: "Event Title",
date: "2023-02-14T08:10:46.676Z"
attendees: ["Apple"]
}
这不是我们想要, date 应该是 Date 对象,而不是字符串。这是因为 JSON.stringify
只能处理基础对象,数组和基本类型。其他类型都将被转换,如时间变成字符串, Set 则简单的变成 {}
,甚至直接忽略 undefined
和 函数。
例如:
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))
// console.log(veryProblematicCopy )
{
"set": {},
"map": {},
"regex": {},
"deep": {
"array": [
{}
]
},
"error": {},
}
我们还必须删除原来的循环引用, 否则 JSON.stringify 会报错。
因此,如果我们的需求符合它可以做的事情,这个方法可能很好。
_.cloneDeep ?
Lodash 的 cloneDeep
函数称为一个通用的解决方案。结果也正如期望的那样。
import cloneDeep from 'lodash/cloneDeep'
const calendarEvent = {
title: "Event Title",
date: new Date(),
attendees: ["Steve"]
}
// ✅ All good!
const clonedEvent = structuredClone(calendarEvent)
只有一个问题, 这个包 17.4k(gzipped: 5.3k)。
假设你只导入了这个函数。如果你没有意识到摇树优化并不总是像你希望的那样工作,而是以更常用的方式导入,那么你可能会不小心为这个函数导入25kb的文件。
如果浏览器已经内置了 structuredClone,我们就没有必要那么做。
什么不能被 structuredClone 克隆
函数不能被克隆。
会抛出异常 DataCloneError
DOM 节点 不能被克隆
也会抛出 DataCloneError
属性 descriptors, setters, and getters 不能被克隆
对象原型不能被克隆
所有支持的类型:
JS Built-ins
Array, ArrayBuffer, Boolean, DataView, Date, Error types (those specifically listed below), Map , Object but only plain objects (e.g. from object literals), Primitive types, except symbol (aka number, string, null, undefined, boolean, BigInt), RegExp, Set, TypedArray
Error types
Error, EvalError, RangeError, ReferenceError , SyntaxError, TypeError, URIError
Web/API types
AudioData, Blob, CryptoKey, DOMException, DOMMatrix, DOMMatrixReadOnly, DOMPoint, DomQuad, DomRect, File, FileList, FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemHandle, ImageBitmap, ImageData, RTCCertificate, VideoFrame