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