本文創做於 2018-12-12,2019-12-20 遷移至此
Web Worker (工做線程) 是 HTML5 中提出的概念,分爲兩種類型,專用線程(Dedicated Web Worker) 和共享線程(Shared Web Worker)。專用線程僅能被建立它的腳本所使用(一個專用線程對應一個主線程),而共享線程可以在不一樣的腳本中使用(一個共享線程對應多個主線程)。javascript
專用線程能夠看作是默認狀況的 Web Worker,其加上修飾詞的目的是爲了與共享線程進行區分。本文會較爲嚴格地區分二者,可能較爲累贅,但我的認爲這是必要的。若是單純以 Web Worker
字樣出現的地方指的是二者都會有的狀況。html
Web Worker 的意義在於能夠將一些耗時的數據處理操做從主線程中剝離,使主線程更加專一於頁面渲染和交互。html5
根據 CanI Use 網站的統計,目前約有 93.05% 的瀏覽器支持專用線程。java
而對於共享線程,則僅有大約 41.66% 的瀏覽器支持。web
因爲專用線程和共享線程的構造方法都包含在 window 對象中,咱們在使用二者以前能夠對瀏覽器的支持性進行判斷。算法
if (window.Worker) { // ... }
if (window.SharedWorker) { // ... }
專用線程由 Worker()
方法建立,能夠接收兩個參數,第一個參數是必填的腳本的位置,第二個參數是可選的配置對象,能夠指定 type
、credentials
、name
三個屬性。canvas
var worker = new Worker('worker.js') // var worker = new Worker('worker.js', { name: 'dedicatedWorker'})
共享線程使用 Shared Worker()
方法建立,一樣支持兩個參數,用法與 Worker()
一致。數組
var sharedWorker = new SharedWorker('shared-worker.js')
值得注意的是,由於 Web Worker 有同源限制,因此在本地調試的時候也須要經過啓動本地服務器的方式訪問,使用 file://
協議直接打開的話將會拋出異常。瀏覽器
Worker 線程和主線程都經過 postMessage()
方法發送消息,經過 onmessage
事件接收消息。在這個過程當中數據並非被共享的,而是被複制的。值得注意的是 Error
和 Function
對象不能被結構化克隆算法複製,若是嘗試這麼作的話會致使拋出 DATA_CLONE_ERR
的異常。另外,postMessage()
一次只能發送一個對象, 若是須要發送多個參數能夠將參數包裝爲數組或對象再進行傳遞。服務器
關於 postMessage()
和結構化克隆算法(The structured clone algorithm)將在本文最後進行闡述。
下面是專用線程數據傳遞的示例。
// 主線程 var worker = new Worker('worker.js') worker.postMessage([10, 24]) worker.onmessage = function(e) { console.log(e.data) } // Worker 線程 onmessage = function (e) { if (e.data.length > 1) { postMessage(e.data[1] - e.data[0]) } }
在 Worker 線程中,self
和 this
都表明子線程的全局對象。對於監聽 message
事件,如下的四種寫法是等同的。
// 寫法 1 self.addEventListener('message', function (e) { // ... }) // 寫法 2 this.addEventListener('message', function (e) { // ... }) // 寫法 3 addEventListener('message', function (e) { // ... }) // 寫法 4 onmessage = function (e) { // ... }
主線程經過 MessagePort
訪問專用線程和共享線程。專用線程的 port 會在線程建立時自動設置,而且不會暴露出來。與專用線程不一樣的是,共享線程在傳遞消息以前,端口必須處於打開狀態。MDN 上的 MessagePort
關於 start()
方法的描述是:
Starts the sending of messages queued on the port (only needed when using EventTarget.addEventListener; it is implied when using MessagePort.onmessage.)
這句話通過試驗,能夠理解爲 start()
方法是與 addEventListener
配套使用的。若是咱們選擇 onmessage
進行事件監聽,那麼將隱含調用 start()
方法。
// 主線程 var sharedWorker = new SharedWorker('shared-worker.js') sharedWorker.port.onmessage = function(e) { // 業務邏輯 }
var sharedWorker = new SharedWorker('shared-worker.js') sharedWorker.port.addEventListener('message', function(e) { // 業務邏輯 }, false) sharedWorker.port.start() // 須要顯式打開
在傳遞消息時,postMessage()
方法和 onmessage
事件必須經過端口對象調用。另外,在 Worker 線程中,須要使用 onconnect
事件監聽端口的變化,並使用端口的消息處理函數進行響應。
// 主線程 sharedWorker.port.postMessage([10, 24]) sharedWorker.port.onmessage = function (e) { console.log(e.data) } // Worker 線程 onconnect = function (e) { let port = e.ports[0] port.onmessage = function (e) { if (e.data.length > 1) { port.postMessage(e.data[1] - e.data[0]) } } }
能夠在主線程中使用 terminate()
方法或在 Worker 線程中使用 close()
方法關閉 worker。這兩種方法是等效的,但比較推薦的用法是使用 close()
,防止意外關閉正在運行的 Worker 線程。Worker 線程一旦關閉 Worker 後 Worker 將再也不響應。
// 主線程 worker.terminate() // Dedicated Worker 線程中 self.close() // Shared Worker 線程中 self.port.close()
能夠經過在主線程或 Worker 線程中設置 onerror
和 onmessageerror
的回調函數對錯誤進行處理。其中,onerror
在 Worker 的 error
事件觸發並冒泡時執行,onmessageerror
在 Worker 收到的消息不能進行反序列化時觸發(本人通過嘗試沒有辦法觸發 onmessageerror
事件,若是在 worker 線程使用 postMessage
方法傳遞一個 Error 或 Function 對象會由於沒法序列化優先被 onerror
方法捕獲,而根本不會進入反序列化的過程)。
// 主線程 worker.onerror = function () { // ... } // 主線程使用專用線程 worker.onmessageerror = function () { // ... } // 主線程使用共享線程 worker.port.onmessageerror = function () { // ... } // worker 線程 onerror = function () { }
Web Worker 提供了 importScripts()
方法,可以將外部腳本文件加載到 Worker 中。
importScripts('script1.js') importScripts('script2.js') // 以上寫法等價於 importScripts('script1.js', 'script2.js')
Worker 能夠生成子 Worker,但有兩點須要注意。
目前沒有一類標籤可使 Worker 的代碼像 <script>
元素同樣嵌入網頁中,但咱們能夠經過 Blob()
將頁面中的 Worker 代碼進行解析。
<script id="worker" type="javascript/worker"> // 這段代碼不會被 JS 引擎直接解析,由於類型是 'javascript/worker' // 在這裏寫 Worker 線程的邏輯 </script> <script> var workerScript = document.querySelector('#worker').textContent var blob = new Blob(workerScript, {type: "text/javascript"}) var worker = new Worker(window.URL.createObjectURL(blob)) </script>
Web Worker 中,Worker 線程和主線程之間使用結構化克隆算法(The structured clone algorithm)進行數據通訊。結構化克隆算法是一種經過遞歸輸入對象構建克隆的算法,算法經過保存以前訪問過的引用的映射,避免無限遍歷循環。這一過程能夠理解爲,在發送方使用相似 JSON.stringfy()
的方法將參數序列化,在接收方採用相似 JSON.parse()
的方法反序列化。
可是,一次數據傳輸就須要同時通過序列化和反序列化,若是數據量大的話,這個過程自己也可能形成性能問題。所以, Worker 中提出了 Transferable Objects
的概念,當數據量較大時,咱們能夠選擇在將主線程中的數據直接移交給 Worker 線程。值得注意的是,這種轉移是完全的,一旦數據成功轉移,主線程將不能訪問該數據。這個移交的過程仍然經過 postMessage
進行傳遞。
postMessage(message, transferList)
例如,傳遞一個 ArrayBuffer 對象
let aBuffer = new ArrayBuffer(1) worker.postMessage({ data: aBuffer }, [aBuffer])
Worker 工做在一個 WorkerGlobalDataScope
的上下文中。每個 WorkerGlobalDataScope
對象都有不一樣的 event loop
。這個 event loop
沒有關聯瀏覽器上下文(browsing context),它的任務隊列也只有事件(events)、回調(callbacks)和聯網的活動(networking activity)。
每個 WorkerGlobalDataScope
都有一個 closing
標誌,當這個標誌設爲 true
時,任務隊列將丟棄以後試圖加入任務隊列的任務,隊列中已經存在的任務不受影響(除非另有指定)。同時,定時器將中止工做,全部掛起(pending)的後臺任務將會被刪除。
因爲 Worker 工做的上下文不一樣於普通的瀏覽器上下文,所以不能訪問 window 以及 window 相關的 API,也不能直接操做 DOM。Worker 中提供了 WorkerNavigator
和 WorkerLocation
接口,它們分別是 window 中 Navigator
和 Location
的子集。除此以外,Worker 還提供了涉及時間、存儲、網絡、繪圖等多個種類的接口,如下列舉了其中的一部分,更多的接口能夠參考 MDN 文檔。