1. 程式人生 > >PWA(Progressive Web App)入門系列:(五)Web Worker

PWA(Progressive Web App)入門系列:(五)Web Worker

前言

在說Service Worker前有必要說一下Web Worker,因為Service Worker本身就屬於Web Worker的延伸,大部分功能也是基於Web Worker進行的擴充套件。

背景

眾所周知,JavaScript引擎是以單執行緒排程的方式進行,我們無法同時執行多個JavaScript檔案,這種情況下就會導致對硬體資源無法充分利用,並且當在進行一些高耗效能的操作時,會影響主執行緒的其他任務,造成任務阻塞及使用者體驗差等問題。

在這種劣勢情況下,到 2008 年 W3C 提出第一個 HTML5 草案開始,就在 HTML5 中提出了Web Worker的概念,並規範了Web Worker的三大特徵:

  • 能夠長時間執行
  • 理想的啟動效能
  • 理想的記憶體消耗

簡介

Web Worker 是HTML5標準的一部分,這一規範定義了一套 API。實現了 用Web Worker 來實現 JavaScript 的 “多執行緒” 技術,併發執行多個 JavaScript 指令碼。

Web Worker 與傳統多執行緒

每個JavaScript指令碼執行流都稱為一個執行緒,彼此之間互相獨立,並且有瀏覽器中的 JavaScript 引擎負責管理,當然這並不是說JavaScript支援多執行緒,雖然傳統JavaSript有多種方式實現了對多執行緒的模擬(例如:setinterval,setTimeout,以及一些非同步的操作方法等),但是在本質上程式的執行仍然是由 JavaScript 引擎以單執行緒排程的方式執行的,而Web Worker的執行緒是依賴於瀏覽器(宿主環境)來實現的,從而實現了對瀏覽器端多執行緒程式設計的支援。

Web Worker 執行緒種類

Web Worker 有兩種不同執行緒型別,分別是:

  • Dedicated Worker (專用執行緒)。只能被首次生成它的指令碼使用
  • Shared Worker (共享執行緒)。可以同時被多個指令碼使用

通常來說的Web Worker指的就是Dedicated Worker,Service Worker也屬於其中,並且各大瀏覽器對其支援良好,而Shared Worker指的是SharedWorker,目前各大瀏覽器對其支援度較差。

這裡主要對Dedicated Worker進行詳細說明,對於Shared Worker不再進行細說。

Worker模式



Worker執行緒執行流



建立 Web Worker

下面說一下如何建立一個Web Worker

語法:

new Worker(
  in DOMString aStringURL
);

使用上面的方式即可以建立一個Web Worker物件,它執行的是aStringURL中的指令碼。目前大多數瀏覽器是支援data URI的aStringURL的,可以通過URL.createObjectURL(blob)建立。但需要注意的是指令碼必須遵循同源策略。

下面建立一個Worker,Worker的執行指令碼是workerfile.js,建立成功後,它會返回一個新的Worker物件賦值給前面宣告的workerObj變數

var workerObj = new Worker('./workerfile.js');

這裡需要注意, worker執行緒的建立的是非同步的,主執行緒程式碼不會阻塞在這裡去等待worker執行緒去載入、執行相應的指令碼檔案,而是會立即向下執行後面程式碼。

Web Worker例項方法

Worker的例項方法只有兩個:

  • postMessage
  • terminate

postMessage

主執行緒向生成的Worker執行緒傳送資料的方法。

語法:

workerObj.postMessage(aMessage, transferList);
  • aMessage:向Worker執行緒傳送的訊息資料物件。它可以是任何型別的值或JavaScript物件。
  • transferList:可選。Transferable型別的陣列。主要用在 ArrayBuffer, MessagePort, ImageBitmap物件。

注意:

postMessage傳送的aMessage引數,在傳遞通訊的時候會對資料進行克隆,為了防止多個執行緒間的資料同時修改的問題。實際上,瀏覽器內部的實現是,先將通訊傳遞的資料序列化,隨後把序列化後的資料發給子執行緒,後者再將資料還原。

postMessage也可以以二進位制的方式傳輸,例如 ArrayBuffer 、File、Blob、ImageBitmap等物件。但是往往傳輸的這些物件資料量都很大,前面說了傳輸資料會進行拷貝,如果傳一個100MB的資料,那麼瀏覽器預設會再複製一份100MB的資料,導致一些不必要的資源消耗。為了防止這種問題,就可以使用上面說的第二個引數transferList來解決。

順道科普一下Transferable介面。這個介面代表一個能在不同可執行上下文中相互傳遞的物件,例如主執行緒和Worker執行緒。

var arrBuff = new ArrayBuffer(8);
myWorker.postMessage(arrBuff, [arrBuff]);

terminate

語法:

workerObj.terminate()

用於立即終止worker物件的行為,如果worker正在執行著任務也會立即終止。

Web Worker例項屬性

Worker例項包含兩個屬性:

  • onmessage:用來接收worker執行緒傳遞過來的資料事件。
  • onerror:用來接收worker執行緒的錯誤資訊。

onmessage

onmessage屬性表示一個EventHandler事件處理函式,當Worker子執行緒返回一條訊息時被呼叫。

語法:

workerObj.onmessage = function(e) { 
    ... 
}

傳遞來的訊息被封裝在事件的data屬性中。

workerObj.onmessage = function(e) {
  var result = e.data;
}

onerror

onerror屬性是EventListener 一個事件監聽函式,一旦有型別為 error 的 ErrorEvent 從 worker執行緒中冒泡出來時就會執行該函式。可以通過preventDefault()來取消冒泡。

主要用到的錯誤屬性有:

  • message: 可讀的錯誤資訊
  • filename: 發生錯誤的指令碼檔名稱
  • lineno: 發生錯誤的指令碼所在檔案的行數

Web Worker檔案方法

Worker執行緒物件抽象於DedicatedWorkerGlobalScope介面。此作用域下沒有window物件,需要用self來呼叫。

在傳送資料和接收資料使用的方法和worker例項物件的一樣:

  • postMessage
  • onerror

這兩個方法就不說了,還有一個close方法說一下。

close

這個和terminate()有點類似。這個方法主要用來清除所有在WorkerGlobalScope事件環中的排隊任務,關閉特定作用域。

self.close()

importScripts 匯入指令碼

WorkerGlobalScope 物件中可以使用importScripts()方法來進行對指令碼檔案和資源的引入。

但這個操作需要注意:

  • 如果沒有給 importScripts 方法任何引數,那麼立即返回,終止下面的步驟。
  • 解析 importScripts 方法的每一個引數。
  • 如果有任何失敗或者錯誤,丟擲 SYNTAX_ERR 異常。
  • 嘗試從使用者提供的 URL 資源位置處獲取指令碼資源。
  • 對於 importScripts 方法的每一個引數,按照使用者的提供順序,獲取指令碼資源後繼續進行其它操作。

Worker執行緒宣告週期

worker執行緒間的資料傳遞必須依賴於瀏覽器的context環境,通過MessagePort進行傳遞資料,所以每個worker執行緒的全域性作用域都會有埠列表,並且會在WorkerGlobalScope中生成一個worker執行緒的執行緒列表,在初始化時為空。當worker執行緒建立時會被填充進去,當worker執行緒終止時會從這個列表刪除。

worker執行緒中可呼叫的物件

在worker執行緒中,可以獲得下列物件:

  • navigator
  • location
  • XMLHttpRequest
  • setTimeout/setInterval
  • Application Cache
  • fetch
  • atob/btoa

等等。

例項

下面寫一個使用的小例子

html檔案:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<button id="btn">傳送</button>
<script>
    var worker = new Worker('./worker.js')

    btn.onclick = function(){
        worker.postMessage({a:1,b:2,c:3})
    }

    worker.onmessage = function(e){
        console.log('index-msg:', e)
    }

    worker.onerror = function(e) {
        console.log('index-err', e)
        e.preventDefault()
    }
</script>   
</body>
</html>

worker.js檔案

self.onmessage = function(e) {
    console.log('worker in:', e)

    self.postMessage('get postMessage!')
}

相容性

還是有必要列一下Worker目前在瀏覽器上的相容性:



可以看到支援的非常不錯。

總結

可以看到Web Worker的出現使得在 Web 進行多執行緒程式設計成為可能,對於高消耗、耗時長的操作可以放到woker裡面去進行。

所以可以在以下應用場景使用:

  • 使用專用執行緒進行數學運算
  • 影象處理
  • 大量資料的檢索
  • 背景資料分析

等。


部落格名稱:王樂平部落格

CSDN部落格地址:http://blog.csdn.net/lecepin

知識共享許可協議
本作品採用 知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。