Web?Woker與主線程通信場(chǎng)景下對(duì)postMessage的簡(jiǎn)潔封裝詳解
Web Worker與主線程之間進(jìn)行通信
使用postMessage
是一種常見(jiàn)的方式。然而,在某些業(yè)務(wù)場(chǎng)景中,postMessage
可能會(huì)顯得不夠簡(jiǎn)潔,因?yàn)樗婕暗绞謩?dòng)序列化和反序列化數(shù)據(jù),以及通過(guò)事件監(jiān)聽(tīng)器處理消息。以下是一些常見(jiàn)問(wèn)題和解決方案,以簡(jiǎn)化在Web Worker與主線程之間的通信場(chǎng)景中使用postMessage
的問(wèn)題。
結(jié)構(gòu)化克隆問(wèn)題
在Web Worker與主線程之間傳輸數(shù)據(jù)時(shí),使用postMessage()方法進(jìn)行通信,瀏覽器會(huì)對(duì)傳遞的數(shù)據(jù)進(jìn)行序列化和反序列化的過(guò)程,以便在不同的線程間傳遞數(shù)據(jù)。這個(gè)序列化和反序列化的過(guò)程就是結(jié)構(gòu)化克?。⊿tructured Cloning)。
結(jié)構(gòu)化克隆是一種瀏覽器內(nèi)置的序列化和反序列化算法,它可以將復(fù)雜的JavaScript對(duì)象、數(shù)組、字符串、數(shù)字、布爾值等數(shù)據(jù)類型轉(zhuǎn)換成一個(gè)可以在不同線程間傳遞的二進(jìn)制數(shù)據(jù)流,然后再將這個(gè)二進(jìn)制數(shù)據(jù)流反序列化為與原始數(shù)據(jù)相同的JavaScript對(duì)象。
結(jié)構(gòu)化克隆有一些特點(diǎn)和限制:
- 支持的數(shù)據(jù)類型:結(jié)構(gòu)化克隆支持包括對(duì)象、數(shù)組、字符串、數(shù)字、布爾值、日期、正則表達(dá)式、Blob、File、ImageData等常見(jiàn)的JavaScript數(shù)據(jù)類型。但并不支持函數(shù)、Map、Set、Symbol等一些特殊的JavaScript數(shù)據(jù)類型。
- 克隆整個(gè)對(duì)象:結(jié)構(gòu)化克隆會(huì)克隆整個(gè)對(duì)象,包括對(duì)象的所有屬性和方法。這可能會(huì)導(dǎo)致性能開(kāi)銷較大,尤其是在傳輸大規(guī)模數(shù)據(jù)時(shí)。
- 不共享內(nèi)存:結(jié)構(gòu)化克隆會(huì)生成一份完整的副本,而不是共享內(nèi)存。這意味著在主線程和Web Worker之間傳遞數(shù)據(jù)時(shí),會(huì)產(chǎn)生復(fù)制的開(kāi)銷,并且對(duì)數(shù)據(jù)的修改在不同線程中是不共享的。
- 兼容性:結(jié)構(gòu)化克隆在大多數(shù)現(xiàn)代瀏覽器中得到支持,但并不是所有瀏覽器都支持。一些老舊的瀏覽器可能不支持結(jié)構(gòu)化克隆或者只支持部分?jǐn)?shù)據(jù)類型的結(jié)構(gòu)化克隆。
在傳輸過(guò)程中,當(dāng)使用postMessage()方法傳遞數(shù)據(jù)時(shí),瀏覽器會(huì)自動(dòng)使用結(jié)構(gòu)化克隆對(duì)數(shù)據(jù)進(jìn)行序列化和反序列化的過(guò)程,以便在不同線程間傳遞數(shù)據(jù),但結(jié)構(gòu)化克隆可能會(huì)帶來(lái)性能開(kāi)銷和兼容性問(wèn)題,需要根據(jù)具體情況來(lái)選擇合適的解決方案。在不支持結(jié)構(gòu)化克隆的瀏覽器下,使用postMessage()傳輸數(shù)據(jù)需要使用JSON對(duì)數(shù)據(jù)內(nèi)容進(jìn)行字符串轉(zhuǎn)化和解析,這也會(huì)帶來(lái)一定的性能損耗和數(shù)據(jù)類型限制。
優(yōu)化方案
- 分割數(shù)據(jù):將大規(guī)模的數(shù)據(jù)分割成較小的塊進(jìn)行傳遞,而不是一次性傳遞整個(gè)數(shù)據(jù)。例如,可以將大型數(shù)組切割成多個(gè)小塊,分別傳遞給Web Worker,然后在Web Worker中重新組合這些小塊,從而減少單次傳遞的數(shù)據(jù)量。
- 使用共享內(nèi)存:共享內(nèi)存是一種在Web Worker和主線程之間共享數(shù)據(jù)的方式,而無(wú)需進(jìn)行復(fù)制。這樣可以避免結(jié)構(gòu)化克隆的性能開(kāi)銷。共享內(nèi)存可以通過(guò)使用TypedArray和ArrayBuffer來(lái)實(shí)現(xiàn),可以在主線程和Web Worker之間直接共享數(shù)據(jù)的引用,而不需要進(jìn)行復(fù)制。需要注意的是,共享內(nèi)存可能需要使用鎖或其他同步機(jī)制來(lái)確保對(duì)共享數(shù)據(jù)的訪問(wèn)是安全的。
- 使用其他序列化方式:除了結(jié)構(gòu)化克隆,還可以考慮使用其他的序列化方式,例如JSON.stringify和JSON.parse。雖然JSON序列化和反序列化可能比結(jié)構(gòu)化克隆更慢,但它不會(huì)像結(jié)構(gòu)化克隆一樣復(fù)制整個(gè)數(shù)據(jù)(因僅支持部分?jǐn)?shù)據(jù)類型,以及會(huì)無(wú)視undefined的字段等),而是將數(shù)據(jù)轉(zhuǎn)換為JSON字符串,并在接收方解析JSON字符串成JavaScript對(duì)象。這樣可以一定的避免復(fù)制大規(guī)模的數(shù)據(jù),從而降低性能開(kāi)銷。
- 使用壓縮算法:對(duì)于大規(guī)模的數(shù)據(jù),可以考慮使用壓縮算法對(duì)數(shù)據(jù)進(jìn)行壓縮,從而減小數(shù)據(jù)的大小,降低傳輸?shù)臄?shù)據(jù)量。在接收方進(jìn)行解壓縮后再進(jìn)行處理。常見(jiàn)的壓縮算法有g(shù)zip、zlib等,可以在主線程和Web Worker之間使用這些算法對(duì)數(shù)據(jù)進(jìn)行壓縮和解壓縮。
postMessage 簡(jiǎn)單封裝
主進(jìn)程封裝
// 定義一個(gè) WorkerMessage 類,用于向 Worker 發(fā)送消息并處理返回結(jié)果 let canStructuredClone; export class WorkerMessage { constructor(workerUrl) { this.worker = new Worker(workerUrl); this.callbacks = new Map(); canStructuredClone === undefined && this.isStructuredCloneSupported(); // 監(jiān)聽(tīng)從 Worker 返回的消息 this.worker.addEventListener('message', event => { const {id, type, payload} = event.data; const callback = this.callbacks.get(id); if (!callback) { console.warn(`未知的消息 ID:${id}`); return; } switch (type) { case 'SUCCESS': callback.resolve(payload); break; case 'ERROR': callback.reject(payload); break; default: console.warn('未知的消息類型:', type); } this.callbacks.delete(id); }); } // 發(fā)送消息給 Worker postMessage(payload) { const id = Date.now().toString(36) + Math.random().toString(36).substr(2); const message = canStructuredClone ? {id, payload} : JSON.stringify({id, payload}) this.worker.postMessage(message); return new Promise((resolve, reject) => { this.callbacks.set(id, {resolve, reject}); }); } // 關(guān)閉 Worker terminate() { this.worker.terminate(); } // 判斷當(dāng)前瀏覽器是否支持結(jié)構(gòu)化克隆算法 isStructuredCloneSupported() { try { const obj = {data: 'Hello'}; const clonedObj = window.postMessage ? window.postMessage(obj, '*') : obj; return canStructuredClone = clonedObj !== obj; } catch (error) { // 捕獲到異常,說(shuō)明瀏覽器不支持結(jié)構(gòu)化克隆 return canStructuredClone = false; } } }
在上面的代碼中,我們定義了一個(gè)名為 WorkerMessage
的類,用于向 Worker 發(fā)送消息并處理返回結(jié)果。在該類的構(gòu)造函數(shù)中,我們首先創(chuàng)建了一個(gè) Worker 實(shí)例,并監(jiān)聽(tīng)了 message 事件。我們使用一個(gè) Map 對(duì)象來(lái)保存每個(gè)消息的回調(diào)函數(shù),以便后續(xù)能夠根據(jù)消息 ID 找到對(duì)應(yīng)的回調(diào)函數(shù)。當(dāng)從 Worker 返回的消息中包含了 ID 時(shí),我們從 Map 中找到對(duì)應(yīng)的回調(diào)函數(shù),并根據(jù)消息的類型分別調(diào)用 resolve 和 reject 方法。在調(diào)用這些方法后,我們需要從 Map 中刪除對(duì)應(yīng)的回調(diào)函數(shù),以避免內(nèi)存泄漏。
在 WorkerMessage
類中,我們定義了一個(gè) postMessage
方法,用于向 Worker 發(fā)送消息并處理返回結(jié)果。在該方法中,我們首先生成一個(gè)唯一的消息 ID,并構(gòu)造了要發(fā)送給 Worker 的消息。然后我們使用 worker.postMessage 方法發(fā)送該消息,并返回一個(gè) Promise 對(duì)象,以便業(yè)務(wù)層進(jìn)行異步處理。在該 Promise
對(duì)象中,我們使用 callbacks.set 方法將該消息 ID 和對(duì)應(yīng)的回調(diào)函數(shù)保存到 Map 中。
在 WorkerMessage
類中,我們還定義了一個(gè) terminate
方法,用于關(guān)閉 Worker 實(shí)例。該方法會(huì)調(diào)用 worker.terminate
方法來(lái)關(guān)閉 Worker。
同時(shí),我們使用 isStructuredCloneSupported
方法判斷當(dāng)前瀏覽器or環(huán)境是否支持結(jié)構(gòu)化克隆,以外部 canStructuredClone
進(jìn)行標(biāo)記,并只在對(duì)象首次實(shí)例化的時(shí)候進(jìn)行復(fù)制。如果當(dāng)前瀏覽器不支持結(jié)構(gòu)化克隆,則postMessage
使用JSON.stringify
轉(zhuǎn)換成字符串。
子進(jìn)程封裝類const res = this.configtype);
export class childMessage { constructor(self, config) { this.self = self; this.config = config; } // 監(jiān)聽(tīng)從主線程傳來(lái)的消息 addEventListener(callback) { this.self.addEventListener('message', event => { const {id, payload} = event.data; const {type} = payload; try { const res = this.config[type](canStructuredClone ? payload.payload : JSON.parse(payload.payload)); if (res instanceof Promise) { res.then(data => { this.self.postMessage({ id, type: 'SUCCESS', payload: canStructuredClone ? data : JSON.stringify(data) }); }).catch(e => { this.self.postMessage({id, type: 'ERROR', payload: e.toString()}); }); } else { this.self.postMessage({ id, type: 'SUCCESS', payload: canStructuredClone ? res : JSON.stringify(res) }); } } catch (e) { this.self.postMessage({id, type: 'ERROR', payload: e.toString()}); } finally { callback?.(); } }); } }
這個(gè)子進(jìn)程消息傳遞的類,通過(guò)監(jiān)聽(tīng)主線程發(fā)送的消息,并使用傳入的 config
對(duì)象處理不同類型的消息,主進(jìn)程通過(guò)指定執(zhí)行函數(shù)的type,由worker來(lái)調(diào)用制定的函數(shù)。其中,callback
參數(shù)是一個(gè)可選的回調(diào)函數(shù),在處理完一條消息后可以執(zhí)行。其中addEventListener(callback)
通過(guò)添加一個(gè)消息監(jiān)聽(tīng)器,接收一個(gè)回調(diào)函數(shù)作為參數(shù)。在這個(gè)方法中,通過(guò)調(diào)用 addEventListener
方法,監(jiān)聽(tīng)主線程發(fā)送過(guò)來(lái)的消息。然后對(duì)收到的消息進(jìn)行處理,并將處理結(jié)果返回給主線程。如果結(jié)果是一個(gè) Promise,則使用 then
方法處理異步結(jié)果,并將結(jié)果發(fā)送給主線程。如果結(jié)果是一個(gè)普通值,則直接將結(jié)果發(fā)送給主線程。在處理完一條消息后,會(huì)執(zhí)行可選的 callback
回調(diào)函數(shù)。
同時(shí)也使用了canStructuredClone
,如果瀏覽器支持結(jié)構(gòu)化克隆(structured clone)算法,則直接將 payload 傳給處理函數(shù)。否則,將 payload
進(jìn)行 JSON 轉(zhuǎn)換,并將其傳給處理函數(shù)。
使用案例
主進(jìn)程
// 創(chuàng)建一個(gè) WorkerMessage 實(shí)例,并指定要加載的 Worker 文件路徑 import {WorkerMessage} from "../index.js"; console.log('WorkerMessage start') const worker = new WorkerMessage('./worker.js'); // 發(fā)送一個(gè)消息給 Worker,并處理返回結(jié)果 worker.postMessage({type: 'CALCULATE', payload: 10}).then( result => { console.log('計(jì)算結(jié)果:', result); }, error => { console.error('計(jì)算出錯(cuò):', error); } ); // 關(guān)閉 Worker 實(shí)例 // worker.terminate(); worker.postMessage({type: 'PLUS', payload: 10}).then( result => { console.log('計(jì)算結(jié)果:', result); }, error => { console.error('計(jì)算出錯(cuò):', error); } );
worker.js worker進(jìn)程
import {childMessage} from "../index.js"; // 執(zhí)行計(jì)算的函數(shù) function doCalculate(num) { // 這里可以執(zhí)行一些復(fù)雜的計(jì)算任務(wù) return num * 2; } function doPlus(num) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(num + 1); }, 1000); }); } const child = new childMessage(self, { CALCULATE: doCalculate, PLUS: doPlus }); // 起到分發(fā)執(zhí)行的效果 child.addEventListener(()=>{ console.log('worker is listened'); });
輸出
以上就是Web Woker與主線程通信場(chǎng)景下對(duì)postMessage的簡(jiǎn)潔封裝詳解的詳細(xì)內(nèi)容,更多關(guān)于Web Woker封裝postMessage的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS+HTML5 Canvas實(shí)現(xiàn)簡(jiǎn)單的寫(xiě)字板功能示例
這篇文章主要介紹了JS+HTML5 Canvas實(shí)現(xiàn)簡(jiǎn)單的寫(xiě)字板功能,結(jié)合實(shí)例形式分析了js結(jié)合HTML5 canvas特性的圖形繪制相關(guān)操作技巧,需要的朋友可以參考下2018-08-08Javascript 詳解封裝from表單數(shù)據(jù)為json串進(jìn)行ajax提交
這篇文章主要介紹了Javascript 詳解封裝from表單數(shù)據(jù)為json串進(jìn)行ajax提交的相關(guān)資料,需要的朋友可以參考下2017-03-03Javascript 實(shí)現(xiàn)全屏滾動(dòng)實(shí)例代碼
這篇文章主要介紹了Javascript 實(shí)現(xiàn)全屏滾動(dòng)實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-12-12淺談javascript六種數(shù)據(jù)類型以及特殊注意點(diǎn)
這篇文章主要介紹了javascript六種數(shù)據(jù)類型以及特殊注意點(diǎn),有需要的朋友可以參考一下2013-12-12百度地圖JavascriptApi Marker平滑移動(dòng)及車(chē)頭指向行徑方向
本文主要介紹了百度地圖JavascriptApi Marker平滑移動(dòng)及車(chē)頭指向行徑方向的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-03-03