相關係列: 從零開始的前端築基之旅(面試必備,持續更新~)javascript
Javascript是運行在單線程環境中,也就是說沒法同時運行多個腳本。假設用戶點擊一個按鈕,觸發了一段用於計算的Javascript代碼,那麼在這段代碼執行完畢以前,頁面是沒法響應用戶操做的。html
Web Worker爲Web內容在後臺線程中運行腳本提供了一種簡單的方法。線程能夠執行任務而不干擾用戶界面。前端
Web Worker (工做線程) 是 HTML5 中提出的概念,它讓咱們能夠在頁面運行的 JavaScript 主線程中加載運行另外單獨的一個或者多個 JavaScript 線程;java
Web Worker分爲兩種類型,專用線程(Dedicated Web Worker) 和共享線程(Shared Web Worker)。專用線程僅能被建立它的腳本所使用(一個專用線程對應一個主線程),而共享線程可以在不一樣的腳本中使用(一個共享線程對應多個主線程)。git
Web Worker 提供的多線程編程能力與們傳統意義上的多線程編程(Java、C++ 等)不一樣,主程序線程和 Worker 線程之間,Worker 線程之間,不會共享任何做用域或資源,它們間惟一的通訊方式就是一個基於事件監聽機制的 message。github
JavaScript 語言自己還是運行在單線程上的, Web Worker 只是瀏覽器(宿主環境)提供的一個能力/API。web
Web Worker 的實現爲前端程序帶來了後臺計算的能力,咱們能夠將一些耗時的數據處理操做從主線程中剝離,從而極大減輕了因計算量大形成 UI 阻塞而出現的界面渲染卡、掉幀的狀況,使主線程更加專一於頁面渲染和交互,更大程度地利用了終端硬件的性能;面試
window
對象的默認方法和屬性。可是可使用WebSockets,IndexedDB以及FireFox OS專用的Data Store API等數據存儲機制Message
事件的data屬性中)。這個過程當中數據並非被共享而是被複製。只要運行在同源的父頁面中,workers能夠依次生成新的workers;編程
基本知識瞭解了,下面進行枯燥的使用講解。canvas
建立一個新的worker很簡單。調用Worker()
的構造器,指定一個腳本的URI來執行worker線程(main.js):
const myWorker = new Worker('worker.js');
複製代碼
爲了更好的錯誤處理控制以及向下兼容,將worker運行代碼包裹在如下代碼中是一個很好的想法(main.js):
if (window.Worker) {
...
}
複製代碼
Worker 線程和主線程都經過 postMessage()
方法發送消息,經過 onmessage
事件接收消息。
在主線程中使用時,
onmessage
和postMessage()
必須掛在worker對象上,而在worker中使用時不須要這樣作。在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) {
// ...
}
複製代碼
在主頁面與 worker 之間傳遞的數據是經過拷貝,而不是共享來完成的。
// main.js
const myWorker = new Worker('worker.js')
myWorker.onmessage = function(e) {
console.log(e.data) // 24
}
myWorker.postMessage([10, 24])
// Worker.js
onmessage = function (e) {
const data = e.data
postMessage(data[0] + data[1])
}
複製代碼
傳遞給 worker
的對象須要通過序列化,接下來在另外一端還須要反序列化。頁面與 worker
**不會共享同一個實例,最終的結果就是在每次通訊結束時生成了數據的一個副本。**大部分瀏覽器使用結構化拷貝來實現該特性。
worker線程修改data數據不影響主線程中原始對象
另外一種性能更高的方法是將特定類型的對象(可轉讓對象) 傳遞給一個 worker/從 worker 傳回 。可轉讓對象從一個上下文轉移到另外一個上下文而不會通過任何拷貝操做。這意味着當傳遞大數據時會得到極大的性能提高。
與按照引用傳遞不一樣的是,一旦對象轉讓,那麼它在原來上下文的那個版本將不復存在。該對象的全部權被轉讓到新的上下文內。例如,當你將一個 ArrayBuffer 對象從主應用轉讓到 Worker 中,原始的 ArrayBuffer
被清除而且沒法使用。它包含的內容會(完整無差的)傳遞給 Worker 上下文。
var uInt8Array = new Uint8Array(1024*1024*32); // 32MB
for (var i = 0; i < uInt8Array .length; ++i) {
uInt8Array[i] = i;
}
const myWorker = new Worker('worker.js')
myWorker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);
console.log(uInt8Array.length); // 傳遞後長度:0
複製代碼
能夠調用worker的terminate
方法從主線程中馬上終止一個運行中的worker:
myWorker.terminate();
複製代碼
worker 線程會被當即殺死,不會有任何機會讓它完成本身的操做或清理工做。
worker線程內也能夠調用本身的 close
方法進行關閉:
close();
複製代碼
能夠經過在主線程或 Worker 線程中設置 onerror
和 onmessageerror
的回調函數對錯誤進行處理。
當 worker 出現運行中錯誤時,它的 onerror
事件處理函數會被調用。它會收到一個擴展了 ErrorEvent
接口的名爲 error
的事件。
該事件不會冒泡而且能夠被取消;爲了防止觸發默認動做,worker 能夠調用錯誤事件的 preventDefault()
方法。
// main.js
myWorker.onerror = function () {
// ...
}
myWorker.onmessageerror = function () {
// ...
}
// worker.js
onerror = function () {
}
複製代碼
錯誤事件有如下三個字段:
message
可讀性良好的錯誤消息。filename
發生錯誤的腳本文件名。lineno
發生錯誤時所在腳本文件的行號。若是須要的話 worker 可以生成更多的 worker。這就是所謂的subworker,它們必須託管在同源的父頁面內。並且,subworker 解析 URI 時會相對於父 worker 的地址而不是自身頁面的地址。這使得 worker 更容易記錄它們之間的依賴關係。
Worker 線程可以訪問一個全局函數importScripts()
來引入腳本,該函數接受0個或者多個URI做爲參數來引入資源;如下例子都是合法的:
importScripts(); /* 什麼都不引入 */
importScripts('foo.js'); /* 只引入 "foo.js" */
importScripts('foo.js', 'bar.js'); /* 引入兩個腳本 */
複製代碼
腳本的下載順序不固定,但執行時會按照傳入
importScripts()
中的文件名順序進行。
目前沒有一類標籤可使 Worker 的代碼像 <script>
元素同樣嵌入網頁中,可是若是一個 <script>
元素沒有 src 特性,而且它的
type
特性沒有指定成一個可運行的 mime-type,那麼它就會被認爲是一個數據塊元素,而且可以被 JavaScript 使用。咱們能夠經過 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>
複製代碼
固然,你也能夠經過下面方式來使用:
var myTask = ` onmessage = function (e) { var data = e.data; console.log('worker:', data); }; `;
var blob = new Blob([myTask]);
var myWorker = new Worker(window.URL.createObjectURL(blob));
複製代碼
workers 運行在另外一個全局上下文中,不一樣於當前的window
.
在專用workers的狀況下,
DedicatedWorkerGlobalScope
對象表明了worker的上下文。Worker
全局做用域)能夠經過 self
關鍵字來訪問 。DedicatedWorkerGlobalScope
對象都有不一樣的 event loop
。這個 event loop
沒有關聯瀏覽器上下文(browsing context),它的任務隊列也只有事件(events)、回調(callbacks)和聯網的活動(networking activity)。除了標準的 JavaScript 函數集 (例如 String
, Array
, Object
, JSON
等), DOM有多種功能可供 workers使用。DedicatedWorkerGlobalScope
像Window,實現WindowTimers
和 WindowBase64
。
導航相關
Navigator
Location
時間相關
存儲相關
網絡相關
其餘
一個共享worker能夠被多個腳本使用。
共享worker能夠被多個瀏覽上下文調用,全部這些瀏覽上下文必須屬於同源(相同的協議,主機和端口號)。在本地調試的時候也須要經過啓動本地服務器的方式訪問,使用
file://
協議直接打開的話將會拋出異常。
生成一個新的共享worker與生成一個專用worker很是類似,只是構造器的名字不一樣
const myWorker = new SharedWorker('worker.js');
複製代碼
與一個共享worker通訊必須經過端口對象——一個確切的打開的端口供腳本與worker通訊
在傳遞消息以前,端口鏈接必須被顯式的打開,打開方式是使用onmessage事件處理函數或者start()方法。
即
start()
方法是與addEventListener
配套使用的。若是咱們選擇onmessage
進行事件監聽,那麼將隱含調用start()
方法。
postMessage()
方法必須被端口對象調用
myWorker.port.postMessage([squareNumber.value,squareNumber.value]);
複製代碼
相比於專用Worker,多了個全局的 connect()
函數,在函數中須要去獲取一個 post 對象來進行初始化操做;
onconnect = function(e) {
var port = e.ports[0];
port.onmessage = function(e) {
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
port.postMessage(workerResult);
}
}
複製代碼
Worker
接口會生成真正的操做系統級別的線程,對於 web worker 來講,與其餘線程的通訊點會被很當心的控制,這意味着你很難引發併發問題。你沒有辦法去訪問非線程安全的組件或者是 DOM,此外你還須要經過序列化對象來與線程交互特定的數據。因此你要是不費點勁兒,還真搞不出錯誤來。
除了專用和共享的web worker,還有一些其它類型的worker:
ChromeWorker
。若是你收穫了新知識,請在作側邊欄第一個按鈕用力點一下~
參考文章: