iT邦幫忙

2023 iThome 鐵人賽

DAY 5
0
Modern Web

網頁的另一個大腦:從基礎到進階掌握 Web Worker 技術系列 第 5

使用 structuredClone 將數據複製到 Web worker

  • 分享至 

  • xImage
  •  

主線程與 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 目前已經被各大瀏覽器支持,可能會成為未來深複製時統一的規範,但對於不同物件的複製結果還是不同於常見的其他深複製方式,為了快速理解不同方式間的差異,我寫了個範例比較 structuredClonelodash.cloneDeepJSON.stringify() 的不同點:

範例 Demo
https://ithelp.ithome.com.tw/upload/images/20230919/20162687D56SIWxZeX.png
深複製的三種方式比較

function
structuredCloneJSON.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)structuredClonelodash.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) // {}
};

Symbol
structuredCloneJSON.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()
structuredClonelodash.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
一樣是 structuredClonelodash.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 Element
structuredClone 會丟出錯誤,可能是因為 Web worker 本來就限制操作 DOM 的因素,而 lodash.cloneDeepJSON.stringify() 複製出來的結果是一個空物件

// DOM
const element = document.createElement("div");
table["HTML Element"] = {
  structuredClone: safe(window.structuredClone)(element), // Error
  "lodash.cloneDeep": cloneDeep(element), // {}
  "JSON.stringify": safeJSON(element) // {}
};

File
structuredClone 是唯一一個可以正確複製檔案的,lodash.cloneDeepJSON.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) // {}
};

小結

  1. lodash.cloneDeep 不愧是有名的套件,各種狀況下它都不會丟出錯誤,不需額外做錯誤處理感覺蠻貼心的。
  2. function, symbol, HTML Element 等都無法被 structuredClone 解析,會丟出錯誤,其他也無法解析的物件可參考 Things that don't work with structured clone
  3. 除了以上這些無法被解析的物件外,其餘可以被解析的物件都統稱叫做 Serializable object,這意味著這些物件都可以被 serializeddeserialized,可以做到把資料存在硬碟裡,之後再從硬碟裡把資料取出來。

上一篇
在 Web worker 中使用外部套件
下一篇
postMessage 速度測試
系列文
網頁的另一個大腦:從基礎到進階掌握 Web Worker 技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言