主線程與 worker 線程之間通常會使用 postMessage 傳遞資料,為避免不同的線程操作同一筆資料出現同步問題,實際上背後會執行一個叫做 structuredClone 的算法,類似於將資料 深複製(deep copy) 後再傳遞出去。
主線程
const worker = new Worker('worker.js');
// 背後會執行 structuredClone,複製後再傳遞給 worker 線程
worker.postMessage({
type: '',
payload: 'message'
});
worker 線程
self.ommessage = (e) => {
console.log(e.data) // worker 接收到的是複製的資料
};
structuredClone 目前已經被各大瀏覽器支持,可能會成為未來深複製時統一的規範,但對於不同物件的複製結果還是不同於常見的其他深複製方式,為了快速理解不同方式間的差異,我寫了個範例比較 structuredClone、lodash.cloneDeep、JSON.stringify() 的不同點:
範例 Demo
深複製的三種方式比較
functionstructuredClone
跟 JSON.stringify()
都沒辦法解析,會丟出 Error
const func = function () {};
table["function"] = {
structuredClone: safe(window.structuredClone)(func), // Error
"lodash.cloneDeep": cloneDeep(func),
"JSON.stringify": safeJSON(func) // Error
};
class created by function & class instance
從範例中可以發現,雖然 一般屬性(name) 三個都有複製到,但只有 lodash.cloneDeep
有複製到 prototype 的屬性 (getName)
// class created by function (not plain object)
const FunctionClass = function (name) {
this.name = name;
};
FunctionClass.prototype.getName = "myName";
const functionClassInstance = new FunctionClass("myName");
table["class created by function"] = {
structuredClone: safe(window.structuredClone)(functionClassInstance),
"lodash.cloneDeep": cloneDeep(functionClassInstance),
"JSON.stringify": safeJSON(functionClassInstance)
};
// class instance (not plain object)
class ClassInstance {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
const classInstance = new ClassInstance("myName");
table["class instance"] = {
structuredClone: safe(window.structuredClone)(classInstance),
"lodash.cloneDeep": cloneDeep(classInstance),
"JSON.stringify": safeJSON(classInstance)
};
Map & Set & ArrayBuffer
這三個都算是 Javascript 裡比較不一樣的 物件(object),structuredClone
跟 lodash.cloneDeep
都可以正確複製,但 JSON.stringify()
會變成一個空物件
// Map
const map = new Map([
["1", "one"],
["2", "two"]
]);
table["Map"] = {
structuredClone: window.structuredClone(map), // Map
"lodash.cloneDeep": cloneDeep(map), // Map
"JSON.stringify": safeJSON(map) // {}
};
// Set
const set = new Set([
["1", "one"],
["2", "two"]
]);
table["Set"] = {
structuredClone: window.structuredClone(set), // Set
"lodash.cloneDeep": cloneDeep(set), // Set
"JSON.stringify": safeJSON(set) // {}
};
// array buffer
const arrayBuffer = new ArrayBuffer(8);
table["ArrayBuffer"] = {
structuredClone: window.structuredClone(arrayBuffer), // ArrayBuffer
"lodash.cloneDeep": cloneDeep(arrayBuffer), // ArrayBuffer
"JSON.stringify": safeJSON(arrayBuffer) // {}
};
SymbolstructuredClone
跟 JSON.stringify()
都會丟出錯誤,只有 lodash.cloneDeep
不會丟出錯誤,反而是回傳一個新的 Symbol
// symbol
const symbol = Symbol();
table["symbol"] = {
structuredClone: safe(window.structuredClone)(symbol), // Error
"lodash.cloneDeep": cloneDeep(symbol), // Symbol
"JSON.stringify": safeJSON(symbol) // Error
};
Date()structuredClone
跟 lodash.cloneDeep
都正確的複製了 Date()
函數與它的原型方法,而 JSON.stringify()
直接轉換成字串了
// Date
const date = new Date();
table["Date()"] = {
structuredClone: window.structuredClone(date), // Tue Sep 19 2023 22:34:39 GMT+0800
"lodash.cloneDeep": cloneDeep(date), // Tue Sep 19 2023 22:34:39 GMT+0800
"JSON.stringify": safeJSON(date) // "2023-09-19T14:34:39.894Z"
};
circular reference
一樣是 structuredClone
跟 lodash.cloneDeep
都正確的複製,而 JSON.stringify()
直接丟出錯誤
// circular reference
const circular = { name: "MDN" };
circular.itself = circular;
table["circular reference"] = {
structuredClone: window.structuredClone(circular),
"lodash.cloneDeep": cloneDeep(circular),
"JSON.stringify": safeJSON(circular) // Error
};
HTML ElementstructuredClone
會丟出錯誤,可能是因為 Web worker 本來就限制操作 DOM 的因素,而 lodash.cloneDeep
跟 JSON.stringify()
複製出來的結果是一個空物件
// DOM
const element = document.createElement("div");
table["HTML Element"] = {
structuredClone: safe(window.structuredClone)(element), // Error
"lodash.cloneDeep": cloneDeep(element), // {}
"JSON.stringify": safeJSON(element) // {}
};
FilestructuredClone
是唯一一個可以正確複製檔案的,lodash.cloneDeep
跟 JSON.stringify()
複製出來的結果是一個空物件
// File
const file = new File(["file"], "file.txt", {
type: "text/plain"
});
table["File"] = {
structuredClone: window.structuredClone(file), // File
"lodash.cloneDeep": cloneDeep(file), // {}
"JSON.stringify": safeJSON(file) // {}
};
lodash.cloneDeep
不愧是有名的套件,各種狀況下它都不會丟出錯誤,不需額外做錯誤處理感覺蠻貼心的。function
, symbol
, HTML Element
等都無法被 structuredClone
解析,會丟出錯誤,其他也無法解析的物件可參考 Things that don't work with structured clone。