咱們都知道,JavaScript 是單線程的,在同一時刻只能處理一個任務,咱們會經過 setTimeout()、setInterval()、ajax 和事件處理程序等技術模擬「並行」。但都不是真正意義上的並行:html
Web Worker 是 HTML5 標準的一部分,這一規範定義了一套 API,它容許一段 JavaScript 程序運行在主線程以外的另一個線程中。前端
這在很大程度上利用瞭如今不斷升級的電腦計算能力:可以在同一時間平行處理兩個任務。webpack
游泳、健身瞭解一下: 博客、 前端積累文檔、 公衆號、 GitHub
當咱們有些任務須要花費大量的時間,進行復雜的運算,就會致使頁面卡死:用戶點擊頁面須要很長的時間才能響應,由於前面的任務還未完成,後面的任務只能排隊等待。對用戶來講,這樣的體驗無疑是糟糕的,web worker 就是爲了解決這種花費大量時間的複雜運算而誕生的!git
WebWorker 容許在主線程以外再建立一個 worker 線程,在主線程執行任務的同時,worker 線程也能夠在後臺執行它本身的任務,互不干擾。github
這樣就讓 JS 變成多線程的環境了,咱們能夠把高延遲、花費大量時間的運算,分給 worker 線程,最後再把結果返回給主線程就能夠了,由於時間花費多的任務被 web worker 承擔了,主線程就會很流暢了!web
codepen,這裏我寫了一個 class,裏面有詳細註釋,能夠參考一下。ajax
主線程調用new Worker()
構造函數,新建一個 worker 線程,構造函數的參數是一個 url,生成這個 url 的方法有兩種:數組
腳本文件:瀏覽器
const worker = new Worker('https://~.js');
由於 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 創造了另一個線程,不在主線程上,相應的會有一些限制,咱們沒法使用下列對象:
咱們可使用下列對象/功能:
栗子最下方有。
在 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 運算等就很是方便了,不會產生性能負擔
好比用戶輸入時,咱們在後臺檢索答案,或者幫助用戶聯想,糾錯等操做.
沒有找到具體的制定日期,有篇博客是在 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
參考資料: