<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Web Worker 現(xiàn)狀

          共 10738字,需瀏覽 22分鐘

           ·

          2022-04-11 22:12

          大廠技術(shù)??高級(jí)前端??Node進(jìn)階

          點(diǎn)擊上方?程序員成長(zhǎng)指北,關(guān)注公眾號(hào)

          回復(fù)1,加入高級(jí)Node交流群

          導(dǎo)讀:Web 是單線程的。這讓編寫流暢又靈敏的應(yīng)用程序變得越來越困難。Web Worker 的名聲很臭,但對(duì) Web 開發(fā)者來說,它是解決流暢度問題的 一個(gè)非常重要的工具。讓我們來了解一下 Web Worker 吧。

          我們總是把 Web 和 所謂的 “Native” 平臺(tái)(比如 Android 和 iOS)放在一起比較。Web 是流式的,當(dāng)你第一次打開一個(gè)應(yīng)用程序時(shí),本地是不存在任何可用資源的。這是一個(gè)根本的區(qū)別,這使得很多在 Native 上可用的架構(gòu) 無法簡(jiǎn)單應(yīng)用到 Web 上。

          不過,不管你在關(guān)注在什么領(lǐng)域,都一定使用過或了解過 多線程技術(shù)。iOS 允許開發(fā)者 使用 Grand Central Dispatch 簡(jiǎn)單的并行化代碼,而 Android 通過 新的統(tǒng)一任務(wù)調(diào)度器 WorkManager 實(shí)現(xiàn)同樣的事情,游戲引擎 Unity 則會(huì)使用 job systems 。我上面列舉的這些平臺(tái)不僅支持了多線程,還讓多線程編程變得盡可能簡(jiǎn)單。

          在這篇文章,我將概述為什么我認(rèn)為多線程在 Web 領(lǐng)域很重要,然后介紹作為開發(fā)者的我們能夠使用的多線程原語(yǔ)。除此之外,我還會(huì)談?wù)撘恍┯嘘P(guān)架構(gòu)的話題,以此幫助你更輕松的實(shí)現(xiàn)多線程編程(甚至可以漸進(jìn)實(shí)現(xiàn))。

          無法預(yù)測(cè)的性能問題

          我們的目標(biāo)是保持應(yīng)用程序的 流暢(smooth)靈敏(responsive)。流暢 意味著 穩(wěn)定足夠高 的 幀率。靈敏 意味著 UI 以最低的延遲 響應(yīng) 用戶交互。兩者是保持應(yīng)用程序 優(yōu)雅高質(zhì)量 的 關(guān)鍵因素。

          按照 RAIL 模型,靈敏 意味著響應(yīng)用戶行為的時(shí)間控制在 100ms 內(nèi),而 流暢 意味著屏幕上任何元素移動(dòng)時(shí) 穩(wěn)定在 60 fps。所以,我們作為開發(fā)者 擁有 1000ms/60 = 16.6ms 的時(shí)間 來生成每一幀,這也被稱作 “幀預(yù)算”(frame budget)。

          我剛剛提到了 “我們”,但實(shí)際上是 “瀏覽器” 需要 16.6ms 的時(shí)間 去完成 渲染一幀背后的所有工作。我們開發(fā)者僅僅直接負(fù)責(zé) 瀏覽器實(shí)際工作 的一部分。瀏覽器的工作包括(但不限于):

          • 檢測(cè) 用戶操作的 元素(element)

          • 發(fā)出 對(duì)應(yīng)的事件

          • 運(yùn)行相關(guān)的 JavaScript 時(shí)間處理程序

          • 計(jì)算 樣式

          • 進(jìn)行 布局(layout)

          • 繪制(paint)圖層

          • 將這些圖層合并成一張 最終用戶在屏幕上看到的 圖片

          • (以及更多…)

          好大的工作量啊。

          另一方面,“性能差距” 在不斷擴(kuò)大。旗艦手機(jī)的性能隨著 手機(jī)產(chǎn)品的更新?lián)Q代 變得越來越高。而低端機(jī)型正在變得越來越便宜,這使得之前買不起手機(jī)的人能夠接觸到移動(dòng)互聯(lián)網(wǎng)了。就性能而言,這些低端手機(jī)的性能相當(dāng)于 2012 年的 iPhone。

          為 Web 構(gòu)建的應(yīng)用程序會(huì)廣泛運(yùn)行在性能差異很大的不同設(shè)備上。JavaScript 執(zhí)行完成的時(shí)間取決于 運(yùn)行代碼設(shè)備有多快。不光是 JavaScript,瀏覽器執(zhí)行的其他任務(wù)(如 layout 和 paint)也受制于設(shè)備的性能。在一臺(tái)現(xiàn)代的 iPhone 上運(yùn)行只需要 0.5ms 的任務(wù) 可能 到了 Nokia 2 上需要 10ms。用戶設(shè)備的性能是完全無法預(yù)測(cè)的。

          注:RAIL 作為一個(gè)指導(dǎo)框架至今已經(jīng) 6 年了。你需要注意一點(diǎn),實(shí)際上 60fps 只是一個(gè)占位值,它表示的是用戶的顯示設(shè)備原生刷新率。例如,新的 Pixel 手機(jī) 有 90Hz 的屏幕 而 iPad Pro 的屏幕是 120Hz 的,這會(huì)讓 幀預(yù)算 分別減少到 11.1ms 和 8.3ms。

          更復(fù)雜的是,除了測(cè)算 requestAnimationFrame() 回調(diào)之間的時(shí)間,沒有更好的方法來確定運(yùn)行 app 設(shè)備的刷新率 。

          JavaScript

          JavaScript 被設(shè)計(jì)成 與瀏覽器的主渲染循環(huán)同步運(yùn)行。幾乎所有的 Web 應(yīng)用程序都會(huì)遵循這種模式。這種設(shè)計(jì)的缺點(diǎn)是:執(zhí)行緩慢的 JavaScript 代碼會(huì)阻塞瀏覽器渲染循環(huán)。JavaScript 與瀏覽器的主渲染循環(huán) 同步運(yùn)行可以理解為:如果其中一個(gè)沒有完成,另一個(gè)就不能繼續(xù)。為了讓長(zhǎng)時(shí)間的任務(wù)能在 JavaScript中 協(xié)調(diào)運(yùn)行,一種基于 回調(diào) 以及 后來的 Promise 的 異步模型被建立起來。

          為了保持應(yīng)用程序的 流暢,你需要保證你的 JavaScript 代碼運(yùn)行 連同 瀏覽器做的其他任務(wù)(樣式、布局、繪制…)的時(shí)間加起來不超出設(shè)備的幀預(yù)算。為了保持應(yīng)用程序的 靈敏,你需要確保任何給定的事件處理程序不會(huì)花費(fèi)超過 100ms 的時(shí)間,這樣才能及時(shí)在設(shè)備屏幕上展示變化。在開發(fā)中,即使用自己的設(shè)備實(shí)現(xiàn)上面這些已經(jīng)很困難了,想要在所有的設(shè)備都上實(shí)現(xiàn)這些幾乎是不可能的。

          通常的建議是 “做代碼分割(chunk your code)”,這種方式也可以被稱作 “出讓控制權(quán)(yield)給瀏覽器”。其根本的原理是一樣的:為了給瀏覽器一個(gè)時(shí)機(jī)來進(jìn)入下一幀,你需要將代碼分割成大小相似的塊(chunk),這樣一來,在代碼塊間切換時(shí) 就能將控制權(quán)交還給 瀏覽器 來做渲染。

          有很多種“出讓控制權(quán)(yield)給瀏覽器” 的方法,但是沒有那種特別優(yōu)雅的。最近提出的 任務(wù)調(diào)度 API 旨在直接暴露這種能力。然而,就算我們能夠使用 await yieldToBrowser() (或者類似的其他東西) 這樣的 API 來 出讓控制權(quán),這種技術(shù)本身還是會(huì)存在缺陷:為了保證不超出幀預(yù)算,你需要在足夠小的塊(chunk)中完成業(yè)務(wù),而且,你的代碼每一幀至少要 出讓一次控制權(quán)。

          過于頻繁的出讓控制權(quán) 的 代碼 會(huì)導(dǎo)致 調(diào)度任務(wù)的開銷過重,以至于對(duì)應(yīng)用程序整體性能產(chǎn)生負(fù)面影響。再綜合一下我之前提到的 “無法預(yù)測(cè)的設(shè)備性能”,我們就能得出結(jié)論 — 沒有適合所有設(shè)備的塊(chunk)大小。當(dāng)嘗試對(duì) UI 業(yè)務(wù)進(jìn)行 “代碼分割” 時(shí),你就會(huì)發(fā)現(xiàn)這種方式很成問題,因?yàn)橥ㄟ^出讓控制權(quán)給瀏覽器來分步渲染完整的 UI 會(huì)增加 布局 和 繪制 的總成本。

          Web Workers

          有一種方法可以打破 與瀏覽器渲染線程同步的 代碼執(zhí)行。我們可以將一些代碼挪到另一個(gè)不同的線程。一旦進(jìn)入不同的線程,我們就可以任由 持續(xù)運(yùn)行的 JavaScript 代碼 阻塞,而不需要接受 代碼分割 和 出讓控制權(quán) 所帶來的 復(fù)雜度 和 成本。使用這種方法,渲染進(jìn)程甚至都不會(huì)注意到另一個(gè)線程在執(zhí)行阻塞任務(wù)。在 Web 上實(shí)現(xiàn)這一點(diǎn)的 API就是 Web Worker。通過傳入一個(gè)獨(dú)立的 JavaScript 文件路徑 就可以 創(chuàng)建一個(gè) Web Worker,而這個(gè)文件將在新創(chuàng)建的線程里加載和運(yùn)行。

          const?worker?=?new?Worker("./worker.js");

          在我們深入討論之前,有一點(diǎn)很重要,雖然 Web Workers, Service Worker 和 Worklet 很相似,但是它們完全不是一回事,它們的目的是不同的:

          在這篇文章中,我只討論 Web Workers (經(jīng)常簡(jiǎn)稱為 “Worker”)。Worker 就是一個(gè)運(yùn)行在 獨(dú)立線程里的 JavaScript 作用域。Worker 由一個(gè)頁(yè)面生成(并所有)。

          ServiceWorker 是一個(gè) 短期的 ,運(yùn)行在 獨(dú)立線程里的 JavaScript 作用域,作為一個(gè) 代理(proxy)處理 同源頁(yè)面中發(fā)出的所有網(wǎng)絡(luò)請(qǐng)求。最重要的一點(diǎn),你能通過使用 Service Worker 來實(shí)現(xiàn)任意的復(fù)雜緩存邏輯。除此之外,你也可以利用 Service Worker 進(jìn)一步實(shí)現(xiàn) 后臺(tái)長(zhǎng)請(qǐng)求,消息推送 和 其他那些無需關(guān)聯(lián)特定頁(yè)面的功能。它挺像 Web Worker 的,但是不同點(diǎn)在于 Service Worker 有一個(gè)特定的目的 和 額外的約束。

          Worklet 是一個(gè) API 收到嚴(yán)格限制的 獨(dú)立 JavaScript 作用域,它可以選擇是否運(yùn)行在獨(dú)立的線程上。Worklet 的重點(diǎn)在于,瀏覽器可以在線程間移動(dòng) Worklet。AudioWorklet,CSS Painting API 和 Animation Worklet 都是 Worklet 應(yīng)用的例子。

          SharedWorker 是特殊的 Web Worker,同源的多個(gè) Tab 和 窗口可以引用同一個(gè) SharedWorker。這個(gè) API 幾乎不可能通過 polyfill 的方式使用,而且目前只有 Blink 實(shí)現(xiàn)過。所以,我不會(huì)在本文中深入介紹。

          JavaScript 被設(shè)計(jì)為和瀏覽器同步運(yùn)行,也就是說沒有并發(fā)需要處理,這導(dǎo)致很多暴露給 JavaScript 的 API 都不是 線程安全 的。對(duì)于一個(gè)數(shù)據(jù)結(jié)構(gòu)來說,線程安全意味著它可以被多個(gè)線程并行訪問和操作,而它的 狀態(tài)(state)不會(huì) 被破壞(corrupted)。

          這一般通過 互斥鎖(mutexes) 實(shí)現(xiàn)。當(dāng)一個(gè)線程執(zhí)行操作時(shí),互斥鎖會(huì)鎖定其他線程。瀏覽器 和 JavaScript 引擎 因?yàn)椴惶幚礞i定相關(guān)的邏輯,所以能夠做更多優(yōu)化來讓代碼執(zhí)行更快。另一方面,沒有鎖機(jī)制 導(dǎo)致 Worker 需要運(yùn)行在一個(gè)完全隔離的 JavaScript 作用域,因?yàn)槿魏涡问降臄?shù)據(jù)共享都會(huì) 因缺乏線程安全 而產(chǎn)生問題。

          雖然 Worker 是 Web 的 “線程”原語(yǔ) ,但這里的 “線程” 和在 C++,Java 及其他語(yǔ)言中的非常不同。最大的區(qū)別在于,依賴于隔離環(huán)境 意味著 Worker 沒有權(quán)限 訪問其創(chuàng)建頁(yè)面中其他變量和代碼,反之,后者也無法訪問 Worker 中的變量。數(shù)據(jù)通信的唯一方式就是調(diào)用 API postMessage,它會(huì)將傳遞信息復(fù)制一份,并在接收端 觸發(fā) message 事件。隔離環(huán)境也意味著 Worker 無法訪問 DOM,在Worker 中也就無法更新 UI — 至少在沒有付出巨大努力的情況下(比如 AMP 的 worker-dom)。


          瀏覽器對(duì) Web Worker 的支持可以說是普遍的,即使是 IE10 也支持。但是,Web Worker 的使用率依舊偏低,我認(rèn)為這很大程度上是由于 Worker API 特殊的設(shè)計(jì)。

          JavaScript 的并發(fā)模型

          想要應(yīng)用 Worker ,那么就需要對(duì)應(yīng)用程序的架構(gòu)進(jìn)行調(diào)整。JavaScript 實(shí)際上支持兩種不同的并發(fā)模型,這兩種模型通常被歸類為 “Off-Main-Thread 架構(gòu)”(脫離主線程架構(gòu))。這兩種模型都會(huì)使用 Worker,但是有非常不同的使用方式,每種方式都有自己的權(quán)衡策略。這兩種模型了代表解決問題的兩個(gè)方向,而任何應(yīng)用程序都能在兩者之間找到一個(gè)更合適的。

          并發(fā)模型 #1:Actor

          我個(gè)人傾向于將 Worker 理解為 Actor 模型 中的 Actor。編程語(yǔ)言 Erlang 中對(duì)于 Actor 模型 的實(shí)現(xiàn)可以說是最受歡迎的版本。每個(gè) Actor 都可以選擇是否運(yùn)行在獨(dú)立的線程上,而且完全保有自己操作的數(shù)據(jù)。沒有其他的線程可以訪問它,這使得像 互斥鎖 這樣的渲染同步機(jī)制就變得沒有必要了。Actor 只會(huì)將信息傳播給其他 Actor 并 響應(yīng)它們接收到的信息。

          例如,我會(huì)把 主線程 想象成 擁有并管理 DOM 或者說是 全部 UI 的 Actor。它負(fù)責(zé)更新 UI 和 捕獲外界輸入的事件。還會(huì)有一個(gè) Actor 負(fù)責(zé)管理應(yīng)用程序的狀態(tài)。DOM Actor 將低級(jí)的輸入事件 轉(zhuǎn)換成 應(yīng)用級(jí)的語(yǔ)義化的事件,并將這些事件傳遞給 狀態(tài) Actor 。狀態(tài) Actor 按照接收到的事件 修改 狀態(tài)對(duì)象,可能會(huì)使用一個(gè)狀態(tài)機(jī) 甚至涉及其他 Actor。一旦狀態(tài)對(duì)象被更新,狀態(tài) Actor 就會(huì)發(fā)送一個(gè) 更新后狀態(tài)對(duì)象的拷貝 到 DOM Actor。DOM Actor 就會(huì)按照新的狀態(tài)對(duì)象更新 DOM 了。Paul Lewis 和 我 曾經(jīng)在 2018 年的 Chrome 開發(fā)峰會(huì)上探索過以 Actor 為中心的應(yīng)用架構(gòu) 。

          當(dāng)然,這種模式也不是沒有問題的。例如,你發(fā)送的每一條消息都需要被拷貝。拷貝所花的時(shí)間不僅取決于 消息的大小,還取決于當(dāng)前應(yīng)用程序的運(yùn)行情況。根據(jù)我的經(jīng)驗(yàn),postMessage 通常 “足夠快”,但在某些場(chǎng)景確實(shí)不太行。另一個(gè)問題是,將代碼遷移到 Worker 中可以解放 主線程,但同時(shí)不得不支付通信的開銷,而且 Worker 可能會(huì)在響應(yīng)你的消息之前忙于執(zhí)行其他代碼,我們需要考慮這些問題來做一個(gè)平衡。一不小心,Worker 可能會(huì)給 UI 響應(yīng)帶來負(fù)面影響。

          通過 postMessage 可以傳遞非常復(fù)雜的消息。其底層算法(叫做 “結(jié)構(gòu)化克隆”)可以處理 內(nèi)部帶有循環(huán)的數(shù)據(jù)結(jié)構(gòu) 甚至是 Map 和 Set 。然而,他不能處理 函數(shù) 或者 類,因?yàn)檫@些代碼在 JavaScript 中無法跨作用域共享。有點(diǎn)惱人的是,通過 postMessage 傳一個(gè) 函數(shù) 會(huì)拋出一個(gè) 錯(cuò)誤,然而一個(gè)類被傳遞的話,只會(huì)被靜默的轉(zhuǎn)換為一個(gè)普通的 JavaScript 對(duì)象,并在此過程中丟失所有方法(這背后的細(xì)節(jié)是有意義的,但是超出了本文討論的范圍)。

          另外,postMessage 是一種 “Fire-and-Forget” 的消息傳遞機(jī)制,沒有請(qǐng)求 和 響應(yīng) 的概念。如果你想使用 請(qǐng)求/響應(yīng) 機(jī)制(根據(jù)我的經(jīng)驗(yàn),大多數(shù)應(yīng)用程序架構(gòu)都會(huì)最終讓你不得不這么做),你必須自己搞定。這就是我寫了 Comlink 的原因,這是一個(gè)底層使用 RPC 協(xié)議的庫(kù),它能幫助實(shí)現(xiàn) 主線程 和 Worker 互相訪問彼此對(duì)象。使用 Comlink 的時(shí)候,你完全不用管 postMessage。唯一需要注意的一點(diǎn)是,由于 postMessage 的異步性,函數(shù)并不會(huì)返回結(jié)果,而是會(huì)返回一個(gè) promise。在我看來,Comlink 提煉了 Actor 模式 和 共享內(nèi)存 兩種并發(fā)模型中優(yōu)秀的部分 并 提供給用戶。

          Comlink 并不是魔法,為了使用 RPC 協(xié)議 還是需要使用 postMessage。如果你的應(yīng)用程序最終罕見的由于 postMessage 而產(chǎn)生瓶頸,那么你可以嘗試?yán)?ArrayBuffers 可 被轉(zhuǎn)移(transferred) 的特性。轉(zhuǎn)移 ArrayBuffer 幾乎是即時(shí)的,并同時(shí)完成所有權(quán)的轉(zhuǎn)移:在這個(gè)過程中 發(fā)送方的 JavaScript 作用域會(huì)失去對(duì)數(shù)據(jù)的訪問權(quán)。當(dāng)我實(shí)驗(yàn)在主線程之外運(yùn)行 WebVR 應(yīng)用程序的物理模擬時(shí),用到了這個(gè)小技巧。

          并發(fā)模型 #2:共享內(nèi)存

          就像我之前提到的,傳統(tǒng)的線程處理方式是基于 共享內(nèi)存 的。這種方式在 JavaScript 中是不可行的,因?yàn)閹缀跛械?JavaScript API 都是假定沒有并發(fā)訪問對(duì)象 來設(shè)計(jì)的。現(xiàn)在要改變這一點(diǎn)要么會(huì)破壞 Web,要么會(huì)由于目前同步的必要性導(dǎo)致重大的性能損耗。相反,共享內(nèi)存 這個(gè)概念目前被限制在一個(gè)專有類型:SharedArrayBuffer (或簡(jiǎn)稱 SAB)。

          SAB 就像 ArrayBuffer,是線性的內(nèi)存塊,可以通過 Typed Array 或 DataView 來操作。如果 SAB 通過 postMessage 發(fā)送,那么另一端不會(huì)接收到數(shù)據(jù)的拷貝,而是收到完全相同的內(nèi)存塊的句柄。在一個(gè)線程上的任何修改 在其他所有線程上都是可見的。為了讓你創(chuàng)建自己的 互斥鎖 和 其他的并發(fā)數(shù)據(jù)結(jié)構(gòu),Atomics 提供了各種類型的工具 來實(shí)現(xiàn) 一些原子操作 和 線程安全的等待機(jī)制。

          SAB 的 缺點(diǎn)是多方面的。首先,也是最重要的一點(diǎn),SAB 只是一塊內(nèi)存。SAB 是一個(gè)非常低級(jí)的原語(yǔ),以增加 工程復(fù)雜度 和 維護(hù)復(fù)雜度 作為成本,它提供了高靈活度 和 很多能力。而且,你無法按照你熟悉的方式去處理 JavaScript 對(duì)象 和 數(shù)組。它只是一串字節(jié)。

          為了提升這方面的工作效率,我實(shí)驗(yàn)性的寫了一個(gè)庫(kù) buffer-backed-object。它可以合成 JavaScript 對(duì)象,將對(duì)象的值持久化到一個(gè)底層緩沖區(qū)中。另外,WebAssembly 利用 Worker 和 SharedArrayBuffer 來支持 C++ 或 其他語(yǔ)言 的線程模型。WebAssembly 目前提供了實(shí)現(xiàn) 共享內(nèi)存并發(fā) 最好的方案,但也需要你放棄 JavaScript 的很多好處(和舒適度)轉(zhuǎn)而使用另一種語(yǔ)言,而且通常這都會(huì)產(chǎn)出更多的二進(jìn)制數(shù)據(jù)。

          案例研究: PROXX

          在 2019 年,我和我的團(tuán)隊(duì)發(fā)布了 PROXX,這是一個(gè)基于 Web 的 掃雷游戲,專門針對(duì)功能機(jī)。功能機(jī)的分辨率很低,通常沒有觸摸界面,CPU 性能差勁,也沒有湊乎的 GPU。盡管有這么多限制,這些功能機(jī)還是很受歡迎,因?yàn)樗麄兊氖蹆r(jià)低的離譜 而且 有一個(gè)功能完備的 Web 瀏覽器。因?yàn)楣δ軝C(jī)的流行,移動(dòng)互聯(lián)網(wǎng)得以向那些之前負(fù)擔(dān)不起的人開放。

          為了確保這款游戲在這些功能機(jī)上靈敏流暢運(yùn)行,我們使用了一種 類 Actor 的架構(gòu)。主線程負(fù)責(zé)渲染 DOM(通過 preact,如果可用的話,還會(huì)使用 WebGL)和 捕捉 UI 事件。整個(gè)應(yīng)用程序的狀態(tài) 和 游戲邏輯 運(yùn)行在一個(gè) Worker 中,它會(huì)確認(rèn)你是否踩到雷上了,如果沒有踩上,在游戲界面上應(yīng)該如何顯示。游戲邏輯甚至?xí)l(fā)送中間結(jié)果到 UI 線程 來持續(xù)為用戶提供視覺更新。

          其他好處

          我談?wù)摿?流暢度 和 靈敏度 的重要性,以及如何通過 Worker 來更輕松的實(shí)現(xiàn)這些目標(biāo)。另外一個(gè)外在的好處就是 Web Worker 能幫助你的應(yīng)用程序消耗更少的設(shè)備電量。通過并行使用更多的 CPU 核心,CPU 會(huì)更少的使用 “高性能” 模式,總體來說會(huì)讓功耗降低。來自微軟的 David Rousset 對(duì) Web 應(yīng)用程序的功耗進(jìn)行了探索。

          采用 Web Worker

          如果你讀到了這里,希望你已經(jīng)更好的理解了 為什么 Worker 如此有用。那么現(xiàn)在下一個(gè)顯而易見的問題就是:怎么使用。

          目前 Worker 還沒有被大規(guī)模使用,所以圍繞 Worker 也沒有太多的實(shí)踐和架構(gòu)。提前判斷代碼的哪些部分值得被遷移到 Worker 中是很困難的。我并不提倡使用某種特定的架構(gòu) 而拋棄其他的,但我想跟你分享我的做法,我通過這種方式漸進(jìn)的使用 Worker,并獲得了不錯(cuò)的體驗(yàn):

          大多數(shù)人都使用過 模塊 構(gòu)建應(yīng)用程序,因?yàn)榇蠖鄶?shù) 打包器 都會(huì)依賴 模塊 執(zhí)行 打包 和 代碼分割。使用 Web Worker 構(gòu)建應(yīng)用程序最主要的技巧就是將 UI 相關(guān) 和 純計(jì)算邏輯 的代碼 嚴(yán)格分離。這樣一來,必須存在于主線程的模塊(比如調(diào)用了 DOM API 的)數(shù)量就能減少,你可以轉(zhuǎn)而在 Worker 中完成這些任務(wù)。

          此外,盡量少的依靠同步,以便后續(xù)采用諸如 回調(diào) 和 async/await 等異步模式。如果實(shí)現(xiàn)了這一點(diǎn),你就可以嘗試使用 Comlink 來將模塊從主線程遷移到 Worker 中,并測(cè)算這么做是否能夠提升性能。

          現(xiàn)有的項(xiàng)目想要使用 Worker 的話,可能會(huì)有點(diǎn)棘手。花點(diǎn)時(shí)間仔細(xì)分析代碼中那些部分依賴 DOM 操作 或者 只能在主線程調(diào)用的 API。如果可能的話,通過重構(gòu)刪除這些依賴關(guān)系,并漸近的使用上面我提出的模型。

          無論是哪種情況,一個(gè)關(guān)鍵點(diǎn)是,確保 Off-Main-Thread 架構(gòu) 帶來的影響是可測(cè)量的。不要假設(shè)(或者估算)使用 Worker 會(huì)更快還是更慢。瀏覽器有時(shí)會(huì)以一種莫名其妙的方式工作,以至于很多優(yōu)化會(huì)導(dǎo)致反效果。測(cè)算出具體的數(shù)字很重要,這能幫你做出一個(gè)明智的決定!

          Web Worker 和 打包器(Bundler)

          大多數(shù) Web 現(xiàn)代開發(fā)環(huán)境都會(huì)使用打包器來顯著的提升加載性能。打包器能夠?qū)⒍鄠€(gè) JavaScript 模塊打包到一個(gè)文件中。然而,對(duì)于 Worker,由于它構(gòu)造函數(shù)的要求,我們需要讓文件保持獨(dú)立。我發(fā)現(xiàn)很多人都會(huì)將 Worker 的代碼分離并編碼成 Data URL 或 Blob URL,而不是選擇在 打包器 上下功夫來實(shí)現(xiàn)需求。Data URL 和 Blob URL 這兩種方式都會(huì)帶來大問題:Data URL 在 Safari 中完全無法工作,Blob URL 雖說可以,但是沒有 源(origin) 和 路徑 的概念,這意味路徑的解析和獲取無法正常使用。這是使用 Worker 的另一個(gè)障礙,但是最近主流的打包器在處理 Worker 方面都已經(jīng)加強(qiáng)了不少:

          • Webpack :對(duì)于 Webpack v4,worker-loader 插件讓 Webpack 能夠理解 Worker。而從 Webpack v5 開始,Webpack 可以自動(dòng)理解 Worker 的構(gòu)造函數(shù),甚至可以在 主線程 和 Worker 之間共享模塊 而 避免重復(fù)加載。

          • Rollup :對(duì)于 Rollup,我寫過 rollup-plugin-off-main-thread ,這個(gè)插件能讓 Worker 變得開箱即用

          • Parcel :Parcel 值得特別提一下,它的 v1 和 v2 都支持 Worker 的開箱即用,無需額外配置。

          在使用這些打包器開發(fā)應(yīng)用程序時(shí),使用 ES Module 是很常見的。然而,這又會(huì)帶來新問題。

          Web Worker 和 ES Module

          所有的現(xiàn)代瀏覽器都支持通過?

          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    色偷偷男人天堂 | 黄片日逼能看的视频 | 黄片大全在线观看 | 色婷婷国产在线观看 | 国产成人精品影视 |