webWorker 是什麼[webWorker 快速上手]
我們都知道,JavaScript 是單執行緒的,在同一時刻只能處理一個任務,我們會通過 setTimeout()、setInterval()、ajax 和事件處理程式等技術模擬“並行”。但都不是真正意義上的並行:
Web Worker 是 HTML5 標準的一部分,這一規範定義了一套 API,它允許一段 JavaScript 程式執行在主執行緒之外的另外一個執行緒中。
這在很大程度上利用了現在不斷升級的電腦計算能力:能夠在同一時間平行處理兩個任務。
場景
當我們有些任務需要花費大量的時間,進行復雜的運算,就會導致頁面卡死:使用者點選頁面需要很長的時間才能響應,因為前面的任務還未完成,後面的任務只能排隊等待。對使用者來說,這樣的體驗無疑是糟糕的,web worker 就是為了解決這種花費大量時間的複雜運算而誕生的!
WebWorker 的作用:建立 worker 執行緒
WebWorker 允許在主執行緒之外再建立一個 worker 執行緒,在主執行緒執行任務的同時,worker 執行緒也可以在後臺執行它自己的任務,互不干擾。
這樣就讓 JS 變成多執行緒的環境了,我們可以把高延遲、花費大量時間的運算,分給 worker 執行緒,最後再把結果返回給主執行緒就可以了,因為時間花費多的任務被 web worker 承擔了,主執行緒就會很流暢了!
主執行緒
我們先來看一下栗子:
codepen,這裡我寫了一個 class,裡面有詳細註釋,可以參考一下。
建立 worker 物件:
主執行緒呼叫new Worker()
建構函式,新建一個 worker 執行緒,建構函式的引數是一個 url,生成這個 url 的方法有兩種:
-
指令碼檔案:
const worker = new Worker('https://~.js'); 複製程式碼
因為 worker 的兩個限制:
-
分配給 Worker 執行緒執行的指令碼檔案,必須與主執行緒的指令碼檔案同源。
-
worker 不能讀取本地的檔案(不能開啟本機的檔案系統
file://
),它所載入的指令碼必須來自網路。
可以看到限制還是比較多的,如果要使用這種形式的話,在專案中推薦把檔案放在靜態資料夾中,打包的時候直接拷貝進去,這樣我們就可以拿到固定的連結了,
-
-
字串形式:
const data = ` // worker執行緒 do something `; // 轉成二進位制物件 const blob = new Blob([data]); // 生成url const url = window.URL.createObjectURL(blob); // 載入url const worker = new Worker(url); 複製程式碼
栗子中就是使用這種形式的,方便我們演示。
在專案中:我們可以把worker執行緒的邏輯寫在js檔案裡面,然後字串化,然後再export、import,配合webpack進行模組化管理,這樣就很容易使用了。
主執行緒的其他 API:
1. 主執行緒與 worker 執行緒通訊:
worker.postMessage({
hello: ['hello', 'world']
});
複製程式碼
它們相互之間的通訊可以傳遞物件和陣列,這樣我們就可以根據相互之間傳遞的資訊來進行一些操作,比如可以設定一個type
屬性,當值為hello
時執行什麼函式,當值為world
的時候執行什麼函式。
值得注意的是:它們之間通訊是通過拷貝的形式來傳遞資料的,進行傳遞的物件需要經過序列化,接下來在另一端還需要反序列化。這就意味著:
- 我們不能傳遞不能被序列化的資料,比如函式,會丟擲錯誤的。
- 在一端改變資料,另外一端不會受影響,因為資料不存在引用,是拷貝過來的。
2. 監聽 worker 執行緒返回的資訊
worker.onmessage = function (e) {
console.log('父程式接收的資料:', e.data);
// doSomething();
}
複製程式碼
3. 主執行緒關閉 worker 執行緒
Worker 執行緒一旦新建成功,就會始終執行,這樣有利於隨時響應主執行緒的通訊。
這也是 Worker 比較耗費計算機的計算資源(CPU
)的原因,一旦使用完畢,就應該關閉 worker 執行緒。
worker.terminate(); // 主執行緒關閉worker執行緒
複製程式碼
4. 監聽錯誤
// worker執行緒報錯
worker.onerror = e => {
// e.filename - 發生錯誤的指令碼檔名;e.lineno - 出現錯誤的行號;以及 e.message - 可讀性良好的錯誤訊息
console.log('onerror', e);
};
複製程式碼
也可以像我給出的栗子一樣,把兩個報錯放在一起寫,有報錯把資訊傳出來就好了。
Worker 執行緒
self 代表 worker 程式自身
worker 執行緒的執行上下文是一個叫做WorkerGlobalScope
的東西跟主執行緒的上下文(window)不一樣。
我們可以使用self
/WorkerGlobalScope
來訪問全域性物件。
監聽主執行緒傳過來的資訊:
self.onmessage = e => {
console.log('主執行緒傳來的資訊:', e.data);
// do something
};
複製程式碼
傳送資訊給主執行緒
self.postMessage({
hello: [ '這條資訊', '來自worker執行緒' ]
});
複製程式碼
worker 執行緒關閉自身
self.close()
複製程式碼
worker 執行緒載入指令碼:
Worker 執行緒能夠訪問一個全域性函式 imprtScripts()來引入指令碼,該函式接受 0 個或者多個 URI 作為引數。
importScripts('http~.js','http~2.js');
複製程式碼
-
指令碼中的全域性變數都能被 worker 執行緒使用。
-
指令碼的下載順序是不固定的,但執行時會按照傳入 importScripts() 中的檔名順序進行,這個過程是同步的。
Worker 執行緒限制
因為 worker 創造了另外一個執行緒,不在主執行緒上,相應的會有一些限制,我們無法使用下列物件:
- window 物件
- document 物件
- DOM 物件
- parent 物件
我們可以使用下列物件/功能:
-
瀏覽器:navigator 物件
-
URL:location 物件,只讀
-
傳送請求:XMLHttpRequest 物件
-
定時器:setTimeout/setInterval,在 worker 執行緒輪詢也是很棒!
-
應用快取:Application Cache
多個 worker 執行緒
-
在主執行緒內可以建立多個 worker 執行緒
栗子最下方有。
-
worker 執行緒內還可以新建 worker 執行緒,使用同源的指令碼檔案建立。
在 worker 執行緒內再新建 worker 執行緒就不能使用
window.URL.createObjectURL(blob)
,需要使用同源的指令碼檔案來建立新的 worker 執行緒,因為我們無法訪問到window
物件。這裡不方便演示,跟在主執行緒建立 worker 執行緒是一個套路,只是改成了指令碼檔案形式建立 worker 執行緒。
執行緒間轉移二進位制資料
因為主執行緒與 worker 執行緒之間的通訊是拷貝關係,當我們要傳遞一個巨大的二進位制檔案給 worker 執行緒處理時(worker 執行緒就是用來幹這個的),這時候使用拷貝的方式來傳遞資料,無疑會造成效能問題。
幸運的是,Web Worker 提供了一中轉移資料的方式,允許主執行緒把二進位制資料直接轉移給子執行緒。這種方式比原先拷貝的方式,有巨大的效能提升。
一旦資料轉移到其他執行緒,原先執行緒就無法再使用這些二進位制資料了,這是為了防止出現多個執行緒同時修改資料的麻煩局面
下方栗子出自淺談 HTML5 Web Worker
// 建立二進位制資料
var uInt8Array = new Uint8Array(1024*1024*32); // 32MB
for (var i = 0; i < uInt8Array .length; ++i) {
uInt8Array[i] = i;
}
console.log(uInt8Array.length); // 傳遞前長度:33554432
// 字串形式建立worker執行緒
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));
// 使用這個格式(a,[a]) 來轉移二進位制資料
myWorker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]); // 傳送資料、轉移資料
console.log(uInt8Array.length); // 傳遞後長度:0,原先執行緒內沒有這個資料了
複製程式碼
二進位制資料有:File、Blob、ArrayBuffer 等型別,也允許在 worker 執行緒之間傳送,這對於影像處理、聲音處理、3D 運算等就非常方便了,不會產生效能負擔
應用場景:
-
數學運算
-
影像、影音等檔案處理
-
大量資料檢索
比如使用者輸入時,我們在後臺檢索答案,或者幫助使用者聯想,糾錯等操作.
-
耗時任務都丟到 webworker 解放我們的主執行緒。
相容:
沒有找到具體的制定日期,有篇部落格是在 10 年的 7 月份寫的,也就是說 web worker 至少出現了八年了,以下相容摘自MDN:
Chrome:4, Firefox:3.5, IE:10.0, Opera:10.6, Safari:4
現在相容還是做的比較好的,如果實在不放心的話:
if (window.Worker) {
...
}else{
...
}
複製程式碼
結語:
Web Worker的出現,給瀏覽器帶來了後臺計算的能力,把耗時的任務分配給worker執行緒來做,在很大程度上緩解了主執行緒UI渲染阻塞的問題,提升頁面效能。
使用起來也不復雜,以後有複雜的問題,記得要丟給我們瀏覽器的後臺(web worker)來處理
看完之後,一定要研究一下文中的栗子,自己鼓搗鼓搗,實踐出真知!
PS: 推薦一下我上個月寫的手摸手教你使用WebSocket,感興趣的可以看一下。
希望看完的朋友可以點個喜歡/關注,您的支援是對我最大的鼓勵。
以上2018.11.25
參考資料: