前端er來學習一下webWorker吧

咱們都知道,JavaScript 是單線程的,在同一時刻只能處理一個任務,咱們會經過 setTimeout()、setInterval()、ajax 和事件處理程序等技術模擬「並行」。但都不是真正意義上的並行:html

Web Worker 是 HTML5 標準的一部分,這一規範定義了一套 API,它容許一段 JavaScript 程序運行在主線程以外的另一個線程中。前端

這在很大程度上利用瞭如今不斷升級的電腦計算能力:可以在同一時間平行處理兩個任務。webpack

游泳、健身瞭解一下: 博客前端積累文檔公衆號GitHub

場景

當咱們有些任務須要花費大量的時間,進行復雜的運算,就會致使頁面卡死:用戶點擊頁面須要很長的時間才能響應,由於前面的任務還未完成,後面的任務只能排隊等待。對用戶來講,這樣的體驗無疑是糟糕的,web worker 就是爲了解決這種花費大量時間的複雜運算而誕生的!git

WebWorker 的做用:建立 worker 線程

WebWorker 容許在主線程以外再建立一個 worker 線程,在主線程執行任務的同時,worker 線程也能夠在後臺執行它本身的任務,互不干擾github

這樣就讓 JS 變成多線程的環境了,咱們能夠把高延遲、花費大量時間的運算,分給 worker 線程,最後再把結果返回給主線程就能夠了,由於時間花費多的任務被 web worker 承擔了,主線程就會很流暢了!web


主線程

咱們先來看一下栗子:

codepen,這裏我寫了一個 class,裏面有詳細註釋,能夠參考一下。ajax

建立 worker 對象:

主線程調用new Worker()構造函數,新建一個 worker 線程,構造函數的參數是一個 url,生成這個 url 的方法有兩種:數組

  1. 腳本文件:瀏覽器

    const worker = new Worker('https://~.js');

    由於 worker 的兩個限制:緩存

    1. 分配給 Worker 線程運行的腳本文件,必須與主線程的腳本文件同源
    2. worker 不能讀取本地的文件(不能打開本機的文件系統file://),它所加載的腳本必須來自網絡。

能夠看到限制仍是比較多的,若是要使用這種形式的話,在項目中推薦把文件放在靜態文件夾中,打包的時候直接拷貝進去,這樣咱們就能夠拿到固定的連接了,

  1. 字符串形式:

    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的時候執行什麼函數。

值得注意的是:它們之間通訊是經過拷貝的形式來傳遞數據的,進行傳遞的對象須要通過序列化,接下來在另外一端還須要反序列化。這就意味着:

  1. 咱們不能傳遞不能被序列化的數據,好比函數,會拋出錯誤的。
  2. 在一端改變數據,另一端不會受影響,由於數據不存在引用,是拷貝過來的。

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');
  1. 腳本中的全局變量都能被 worker 線程使用。
  2. 腳本的下載順序是不固定的,但執行時會按照傳入 importScripts() 中的文件名順序進行,這個過程是同步的。

Worker 線程限制

由於 worker 創造了另一個線程,不在主線程上,相應的會有一些限制,咱們沒法使用下列對象:

  1. window 對象
  2. document 對象
  3. DOM 對象
  4. parent 對象

咱們可使用下列對象/功能

  1. 瀏覽器:navigator 對象
  2. URL:location 對象,只讀
  3. 發送請求:XMLHttpRequest 對象
  4. 定時器:setTimeout/setInterval,在 worker 線程輪詢也是很棒!
  5. 應用緩存:Application Cache

多個 worker 線程

  1. 在主線程內能夠建立多個 worker 線程

    栗子最下方有。

  2. 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 運算等就很是方便了,不會產生性能負擔

應用場景:

  1. 數學運算
  2. 圖像、影音等文件處理
  3. 大量數據檢索

    好比用戶輸入時,咱們在後臺檢索答案,或者幫助用戶聯想,糾錯等操做.

  4. 耗時任務都丟到 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,感興趣的能夠看一下。

但願看完的朋友能夠點個喜歡/關注,您的支持是對我最大的鼓勵。

博客前端積累文檔公衆號GitHub

以上2018.11.25

參考資料:

MDN

Web Worker 使用教程

淺談HTML5 Web Worker

相關文章
相關標籤/搜索