国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁 > 營銷資訊 > 網(wǎng)站運營 > 動態(tài)創(chuàng)建 Web Worker 實踐指南

動態(tài)創(chuàng)建 Web Worker 實踐指南

時間:2023-07-07 15:06:01 | 來源:網(wǎng)站運營

時間:2023-07-07 15:06:01 來源:網(wǎng)站運營

動態(tài)創(chuàng)建 Web Worker 實踐指南:作為前端,在消費接口提供的數(shù)據(jù)時,往往由于數(shù)據(jù)實際分布在不同地方(如一部分存儲在 ODPS ,而另一部分可能更適合在應用初始化時從本地載入內(nèi)存)而需要對數(shù)據(jù)進行區(qū)分處理。當然,交互的實現(xiàn)可能也會需要很重的計算邏輯,而為了加速計算、不阻塞渲染線程,Web Worker 不失為一個很好的選擇。

網(wǎng)上有很多關于 Web Worker 的入門介紹與示例,但要在實際工程中引入,往往還需要一些額外的工作。相比 MDN 上提供的 demo 示例,在實際工程中,我們可能更希望 Web Worker 能解決如下幾個問題:

Can I use Web Worker
本文結(jié)構(gòu)如下:

  1. 一些約定
  2. 談及 Web Worker
  3. Web Worker 構(gòu)造方法舉例
  4. 動態(tài) Worker 的簡單封裝
  5. 調(diào)用區(qū)分優(yōu)化
  6. 總結(jié)
  7. 進一步閱讀
以下開始正文。

0 / 一些約定

本教程將教你寫一個可動態(tài)創(chuàng)建的可復用 Web Worker。在進一步閱讀之前,我假設你已經(jīng)掌握了關于 Web Worker 的一些基本用法,否則建議先閱讀 MDN 提供的使用 Web Workers 文檔,了解一些基礎概念。

與此同時,本文所創(chuàng)建的動態(tài) Worker 均指專有 Worker,不涉及到共享 Worker 及其他類型 Worker 的內(nèi)容。

1 / 談及 Web Worker

我們常說「任何可以使用 JavaScript 來編寫的應用,最終會由 JavaScript 編寫」,但實際移植到 JavaScript 環(huán)境時仍然存在很多制約,比如瀏覽器兼容性、靜態(tài)類型與運行性能等。 隨著 JavaScript 引擎不斷地優(yōu)化,性能已不再是那個最大的瓶頸,而受到瀏覽器 JavaScript 運行時的單線程環(huán)境限制,最大的阻礙貌似來自語言本身。

好在從 HTML5 規(guī)范開始,Web Worker 概念地引入為 JavaScript 引入了線程技術。Web Worker 為 Web 內(nèi)容在后臺線程中運行腳本提供了一種簡單的方法,你可以在后臺執(zhí)行任務而不干擾用戶界面。例如觸發(fā)長時間運行的腳本以處理計算密集型任務,同時卻不會阻礙 UI 或其他腳本處理用戶互動。對于 Web Worker 來說,最正常的創(chuàng)建方式無異于創(chuàng)建兩個 js 文件,一個 main.js 用于 Worker() 構(gòu)造器并處理與 worker 間消息的接受與發(fā)送:

let myWorker = new Worker('worker.js');myWorker.postMessage('Hi, this is a message from main.js');myWorker.onmessage = (e) => { console.log('Message received from worker', JSON.stringify(e));}一個 worker.js 用于響應主線程的消息,包括處理與回傳結(jié)果:

onmessage = (e) => { console.log('Message received from main script'); let workerResult = `Result: ${JSON.stringify(e)}`; console.log('Posting message back to main script'); postMessage(workerResult);}

2 / Web Worker 構(gòu)造方法舉例

在上例中,傳入 Worker 構(gòu)造函數(shù)的參數(shù)是一個具體路徑,但除此外,我們還能傳入其他路徑達到創(chuàng)建 Web Worker 的目的,比如字符串,具體有如下三種方法:

什么是 Blob 對象?它表示一個不可變、原始數(shù)據(jù)的類文件對象,但不局限于 JavaScript 原生格式的數(shù)據(jù),常被用來存儲體量很大的二進制編碼格式的數(shù)據(jù)。你可以使用 Blob() 構(gòu)造函數(shù)從一段字符串中創(chuàng)建一個 Blob 對象:

const debug = {hello: "world"};const blob = new Blob( [JSON.stringify(debug, null, 2)], {type : 'application/json'});而利用 URL.createObjectURL() API 我們可以將 Blob 對象轉(zhuǎn)換為一個對象 URL 傳入 Worker 構(gòu)造函數(shù),如下:

const worker = new Worker(URL.createObjectURL(blob));那么什么又是 data:application/javascript 呢?Data URLs,即前綴為 data: 協(xié)議的的URL,其允許內(nèi)容創(chuàng)建者向文檔中嵌入小文件。這樣的 URL 由四個部分組成:前綴(data:)、指示數(shù)據(jù)類型的 MIME 類型、如果非文本則為可選的 base64 標記、數(shù)據(jù)本身:

data:[<mediatype>][;base64],<data>所以現(xiàn)在你應該知道 application/javascript 所指了,是的,利用這個 URL 我們可以這樣創(chuàng)建 Web Worker:

const response = "onmessage=function(e){postMessage('Worker: '+e.data);}";const worker = new Worker( 'data:application/javascript,' + encodeURIComponent(response) );worker.onmessage = (e) => { alert('Response: ' + e.data);};worker.postMessage('Test');而在 Safari (<6) 與 IE 10 中,eval 作為向后兼容的一種方式,你可以這樣創(chuàng)建 Web Worker:

const response = "onmessage=function(e){postMessage('Worker: '+e.data);}";const worker = new Worker('Worker-helper.js');worker.postMessage(response);其中 Worker-helper.js 代碼如下:

onmessage = (e) => { onmessage = null; // Clean-up eval(e.data);};當然,在使用之前還需要對相應 API 進行兼容判斷,比如 window.URL || window.webkitURL 或者 window.Worker 等,這里便不詳述。

3 / 動態(tài) Worker 的簡單封裝

我們先來寫一個簡單的 Web Worker 示例,假設我們在 Worker 收到數(shù)據(jù)時有一個簡單的判斷邏輯,即只處理 method='format' 的消息:

window.URL = window.URL || window.webkitURL;const response = `onmessage = ({ data: { data } }) => { console.log('Message received from main script'); const {method} = data; if (data.data && method === 'format') { postMessage({ data: { 'res': 'I am a customized result string.', } }); } console.log('Posting message back to main script');}`;const blob = new Blob([response], {type: 'application/javascript'});const worker = new Worker( URL.createObjectURL(blob));// 事件處理worker.onmessage = (e) => { alert(`Response: ${JSON.stringify(e)}`);};worker.postMessage({ method: 'format', data: []});這個 Demo 會建立一個 Web Worker 并向其發(fā)送一段文本,而 Worker 在處理完畢后主線程會把結(jié)果彈窗顯示出來。接下來,我們就用它繼續(xù)操作。

一個動態(tài) Worker 結(jié)構(gòu)應該長成如下這樣,包含構(gòu)造函數(shù)、動態(tài)調(diào)用函數(shù)以及 Worker 銷毀函數(shù),而構(gòu)造函數(shù)中至少應該定義好 Worker 用到的全局變量、數(shù)據(jù)處理函數(shù)以及 onmessage 事件處理函數(shù):

const BASE_DATASETS = '';class DynamicWorker { constructor(worker) { /** * 依賴的全局變量聲明 * 如果 BASE_DATASETS 非字符串形式,可調(diào)用 JSON.stringify 等方法進行處理 * 保證變量的正常聲明 */ const CONSTS = `const base = ${BASE_DATASETS};`; /** * 數(shù)據(jù)處理函數(shù) */ const formatFn = `const formatFn = ${worker.toString()};`; /** * 內(nèi)部 onmessage 處理 */ const onMessageHandlerFn = `self.onmessage = ()=>{}`; /** * 返回結(jié)果 * @param {*} param0 */ const handleResult = () => {} const blob = new Blob( [`(()=>{${CONSTS}${formatFn}${onMessageHandlerFn}})()`], { type: 'text/javascript' } ); this.worker = new Worker(URL.createObjectURL(blob)); this.worker.addEventListener('message', handleResult); URL.revokeObjectURL(blob); } /** * 動態(tài)調(diào)用 */ send(data) {} close() {}}以上代碼有幾點需要解釋下,比如生成 Blob 對象時,由于入?yún)⑹亲址當?shù)組,如果只是調(diào)用 .toString(),便無法拿到函數(shù)名,因此所有字符串采用變量命名的形式定義。接著我們調(diào)用 URL.createObjectURL 生成對象 URL,在創(chuàng)建完 Worker 后調(diào)用 URL.revokeObjectURL() 讓瀏覽器知道不再需要對這個文件保持引用。

URL.revokeObjectURL() 靜態(tài)方法用來釋放一個之前通過調(diào)用 URL.createObjectURL() 創(chuàng)建的已經(jīng)存在的 URL 對象。當你結(jié)束使用某個 URL 對象時,應該通過調(diào)用這個方法來讓瀏覽器知道不再需要保持這個文件的引用了。詳見 MDN API.

內(nèi)部接收與響應消息的函數(shù)應該做邏輯判斷并發(fā)送對應信息返回主線程,我們這樣完善 onMessageHandlerFn

const onMessageHandlerFn = `self.onmessage = ({ data: { data } }) => { console.log('Message received from main script'); const {method} = data; if (data.data && method === 'format') { self.postMessage({ data: formatFn(data.data) }); } console.log('Posting message back to main script');}`;利用 Promise 的鏈式調(diào)用,我們可以隱藏較為瑣碎的事件監(jiān)聽處理程序。來寫一個 send 方法允許開發(fā)者動態(tài)調(diào)用,內(nèi)部我們接收到數(shù)據(jù)后,改變 resolve 的狀態(tài),并返回這個 Promise:

send(data) { const w = this.worker; w.postMessage({ data, }); return new Promise((res) => { this.resolve = res; })}我們定義一個 this.resolve 用于記錄 Promise 的狀態(tài),然后在 Worker 收到響應后便判斷 this.resolve 然后決定是否 resolve 計算結(jié)果:

const handleResult = ({ data: { data } }) => { if (this.resolve) { resolve(data); this.resolve = null; }}如此一來,接下來我們就可以在主進程中這樣調(diào)用 DynamicWorker 了:

import DataWorker from './dynamicWorker.js';const formatFunc = () => { return { 'res': 'I am a customized result string.', }}const worker = new DataWorker(formatFunc);const result = []; // demo 數(shù)據(jù)worker.send({ method: 'format', data: result}).then((e) => { alert(`Response: ${JSON.stringify(e)}`);})

4 / 調(diào)用區(qū)分優(yōu)化

當然,如果我們沒有頻繁調(diào)用 Worker,那么上面的代碼貌似已經(jīng)足夠。但如果你需要短時間多次傳輸數(shù)據(jù)進行處理,那么調(diào)用的多個方法與對應的多個結(jié)果間可能會相互混淆。為什么呢,原因在于我們在構(gòu)造函數(shù)中寫的這行:

this.worker.addEventListener('message', handleResult);這個事件監(jiān)聽處理函數(shù)是區(qū)分不出每次調(diào)用的,在收到消息后它只會執(zhí)行 resolve。那么該如何解決呢?其實也較為簡單,加入一個標志位用于區(qū)分不同調(diào)用即可。

首先,在構(gòu)造函數(shù)里我們加上這么一行:

this.flagMapping = {};簡單起見,我們直接取日期作為標志位 key,改寫后的 send 方法長成這樣:

send(data) { const w = this.worker; const flag = new Date().toString(); w.postMessage({ data, flag, }); return new Promise((res) => { this.flagMapping[flag] = res; })}最后,根據(jù) flag 傳參我們改寫 Worker 內(nèi)部的 onmessage 函數(shù)以及返回結(jié)果函數(shù)的判斷邏輯:

const onMessageHandlerFn = `self.onmessage = ({ data: { data, flag } }) => { console.log('Message received from main script'); const {method} = data; if (data.data && method === 'format') { self.postMessage({ data: formatFn(data.data), flag }); } console.log('Posting message back to main script');}`;// ...const handleResult = ({ data: { data, flag } }) => { const resolve = this.flagMapping[flag]; if (resolve) { resolve(data); delete this.flagMapping[flag]; }}

5 / 總結(jié)

至此,一個可動態(tài)創(chuàng)建、可復用的 Web Worker 便寫完了,大致骨架見附錄,完整代碼見 GIST https://gist.github.com/hijiangtao/22607ea9e5f4dfe504381a99d4134142。

當然,本文還有很多內(nèi)容沒有涉及,比如創(chuàng)建 subworker、比如共享 worker 等等。在處理簡單邏輯時,本文所述的 Web Worker 已夠用,其他就留到下篇文章再去詳細談談吧。

6 / 進一步閱讀

附錄 - DynamicWorker 類

完整代碼與示例見 https://gist.github.com/hijiangtao/22607ea9e5f4dfe504381a99d4134142.

const BASE_DATASETS = '';class DynamicWorker { constructor(worker) { /** * 依賴的全局變量聲明 * 如果 BASE_DATASETS 非字符串形式,可調(diào)用 JSON.stringify 等方法進行處理 * 保證變量的正常聲明 */ const CONSTS = `const base = ${BASE_DATASETS};`; /** * 數(shù)據(jù)處理函數(shù) */ const formatFn = `const formatFn = ${worker.toString()};`; /** * 內(nèi)部 onmessage 處理 */ const onMessageHandlerFn = `self.onmessage = ({ data: { data, flag } }) => { console.log('Message received from main script'); const {method} = data; if (data.data && method === 'format') { self.postMessage({ data: formatFn(data.data), flag }); } console.log('Posting message back to main script'); }`; /** * 返回結(jié)果 * @param {*} param0 */ const handleResult = ({ data: { data, flag } }) => { const resolve = this.flagMapping[flag]; if (resolve) { resolve(data); delete this.flagMapping[flag]; } } const blob = new Blob( [`(()=>{${CONSTS}${formatFn}${onMessageHandlerFn}})()`], { type: 'text/javascript' } ); this.worker = new Worker(URL.createObjectURL(blob)); this.worker.addEventListener('message', handleResult); this.flagMapping = {}; URL.revokeObjectURL(blob); } /** * 動態(tài)調(diào)用 */ send(data) { const w = this.worker; const flag = new Date().toString(); w.postMessage({ data, flag, }); return new Promise((res) => { this.flagMapping[flag] = res; }) } close() { this.worker.terminate(); }}export default DynamicWorker;
個人公眾號 - 微信搜索「黯曉」或掃這個 二維碼

本站專欄 - 初級前端工程師

生活中難免犯錯,請多多指教!

關鍵詞:實踐,指南,創(chuàng)建,動態(tài)

74
73
25
news

版權所有? 億企邦 1997-2025 保留一切法律許可權利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點擊下載Chrome瀏覽器
關閉