咱們都知道,JavaScript 是單線程的,在同一時刻只能處理一個任務,咱們會經過 setTimeout()、setInterval()、ajax 和事件處理程序等技術模擬「並行」。但都不是真正意義上的並行:html
Web Worker 是 HTML5 標準的一部分,這一規範定義了一套 API,它容許一段 JavaScript 程序運行在主線程以外的另一個線程中。前端
這在很大程度上利用瞭如今不斷升級的電腦計算能力:可以在同一時間平行處理兩個任務。webpack
當咱們有些任務須要花費大量的時間,進行復雜的運算,就會致使頁面卡死:用戶點擊頁面須要很長的時間才能響應,由於前面的任務還未完成,後面的任務只能排隊等待。對用戶來講,這樣的體驗無疑是糟糕的,web worker 就是爲了解決這種花費大量時間的複雜運算而誕生的!github
WebWorker 容許在主線程以外再建立一個 worker 線程,在主線程執行任務的同時,worker 線程也能夠在後臺執行它本身的任務,互不干擾。web
這樣就讓 JS 變成多線程的環境了,咱們能夠把高延遲、花費大量時間的運算,分給 worker 線程,最後再把結果返回給主線程就能夠了,由於時間花費多的任務被 web worker 承擔了,主線程就會很流暢了!ajax
codepen,這裏我寫了一個 class,裏面有詳細註釋,能夠參考一下。數組
主線程調用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進行模塊化管理,這樣就很容易使用了。
worker.postMessage({
hello: ['hello', 'world']
});
複製代碼
它們相互之間的通訊能夠傳遞對象和數組,這樣咱們就能夠根據相互之間傳遞的信息來進行一些操做,好比能夠設置一個type
屬性,當值爲hello
時執行什麼函數,當值爲world
的時候執行什麼函數。
值得注意的是:它們之間通訊是經過拷貝的形式來傳遞數據的,進行傳遞的對象須要通過序列化,接下來在另外一端還須要反序列化。這就意味着:
worker.onmessage = function (e) {
console.log('父進程接收的數據:', e.data);
// doSomething();
}
複製代碼
Worker 線程一旦新建成功,就會始終運行,這樣有利於隨時響應主線程的通訊。
這也是 Worker 比較耗費計算機的計算資源(CPU
)的緣由,一旦使用完畢,就應該關閉 worker 線程。
worker.terminate(); // 主線程關閉worker線程
複製代碼
// worker線程報錯
worker.onerror = e => {
// e.filename - 發生錯誤的腳本文件名;e.lineno - 出現錯誤的行號;以及 e.message - 可讀性良好的錯誤消息
console.log('onerror', e);
};
複製代碼
也能夠像我給出的栗子同樣,把兩個報錯放在一塊兒寫,有報錯把信息傳出來就行了。
worker 線程的執行上下文是一個叫作WorkerGlobalScope
的東西跟主線程的上下文(window)不同。
咱們可使用self
/WorkerGlobalScope
來訪問全局對象。
self.onmessage = e => {
console.log('主線程傳來的信息:', e.data);
// do something
};
複製代碼
self.postMessage({
hello: [ '這條信息', '來自worker線程' ]
});
複製代碼
self.close()
複製代碼
Worker 線程可以訪問一個全局函數 imprtScripts()來引入腳本,該函數接受 0 個或者多個 URI 做爲參數。
importScripts('http~.js','http~2.js');
複製代碼
腳本中的全局變量都能被 worker 線程使用。
腳本的下載順序是不固定的,但執行時會按照傳入 importScripts() 中的文件名順序進行,這個過程是同步的。
由於 worker 創造了另一個線程,不在主線程上,相應的會有一些限制,咱們沒法使用下列對象:
咱們可使用下列對象/功能:
瀏覽器:navigator 對象
URL:location 對象,只讀
發送請求:XMLHttpRequest 對象
定時器:setTimeout/setInterval,在 worker 線程輪詢也是很棒!
應用緩存:Application Cache
在主線程內能夠建立多個 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
參考資料: