一起聊聊 JavaScript 中的 structuredClone 現代深拷貝
在JavaScript中,實現深拷貝的方式有很多種,每種方式都有其優點和缺點。今天介紹一種原生JavaScript提供的structuredClone實現深拷貝。
下面列舉一些常見的方式,以及它們的代碼示例和優缺點:
1. 使用JSON.parse(JSON.stringify(obj))
代碼示例:
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
優點:簡單易行,對于大多數對象類型有效。
缺點:不能復制原型鏈,對于包含循環引用的對象可能出現問題。比如以下代碼:
const calendarEvent = {
date: new Date()
}
const problematicCopy = JSON.parse(JSON.stringify(calendarEvent))
最終得到的date不是Data對象,而是字符串。
{
"date": "2024-03-02T03:43:35.890Z"
}
這是因為JSON.stringify只能處理基本的對象、數組。任何其他類型都沒有按預期處理。例如,日期轉換為字符串。Set/Map只是轉換為{}。
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": {},
}
2. 使用遞歸
代碼示例:
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
let clone = obj.constructor();
for (let attr in obj) {
if (obj.hasOwnProperty(attr)) {
clone[attr] = this.deepClone(obj[attr]);
}
}
return clone;
}
優點:對于任何類型的對象都有效,包括循環引用。
缺點:對于大型對象可能會消耗大量內存,并可能導致堆棧溢出。
3. 第三方庫,如 lodash 的 _.cloneDeep 方法
代碼示例:
const _ = require('lodash');
function deepClone(obj) {
return _.cloneDeep(obj);
}
優點:支持更多類型的對象和庫,例如,支持 Proxy 對象。
缺點:會引入依賴導致項目體積增大。
圖片
因為這個函數會導致17.4kb的依賴引入,如果只是引入lodash會更高。
4. 現代深拷貝structuredClone
在現代瀏覽器中,可以使用 structuredClone 方法來實現深拷貝,它是一種更高效、更安全的深拷貝方式。
以下是一個示例代碼,演示如何使用 structuredClone 進行深拷貝:
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)
structuredClone可以做到:
- 拷貝無限嵌套的對象和數組
- 拷貝循環引用
- 拷貝各種各樣的JavaScript類型,如Date、Set、Map、Error、RegExp、ArrayBuffer、Blob、File、ImageData等
哪些不能拷貝:
- 函數
- DOM節點
- 屬性描述、setter和getter
- 對象原型鏈
所支持的完整列表:
Array、ArrayBuffer、Boolean、DataView、Date、Error類型(下面具體列出的類型)、Map、Object,但僅限于普通對象、原始類型,除了symbol(又名number、string、null、undefined、boolean、BigInt)、RegExp、Set、TypedArray
Error類型:
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
值得慶幸的是 structuredClone 在所有主流瀏覽器中都受支持,也支持Node.js和Deno。
圖片
最后
我們現在終于可以直接使用原生JavaScript中的structuredClone能力實現深度拷貝對象。每種方式都有其優缺點,具體使用方式取決于你的需求和目標對象的類型。
參考
- Deep Cloning Objects in JavaScript, the Modern Way(www.builder.io/blog/structured-clone)
- mozilla structuredClone(developer.mozilla.org/zh-CN/docs/Web/API/structuredClone)