web worker

  1. 介紹

    web worker HTML5提供得,運(yùn)行在后臺(tái)的 JavaScript,獨(dú)立于其他腳本,不會(huì)影響頁面的性能。您可以繼續(xù)做任何愿意做的事情:點(diǎn)擊、選取內(nèi)容等等,而此時(shí) web worker 在后臺(tái)運(yùn)行。

  2. 作用

    給JS創(chuàng)造多線程運(yùn)行環(huán)境,允許主線程創(chuàng)建worker線程,分配任務(wù)給后者,主線程運(yùn)行的同時(shí)worker線程也在運(yùn)行,相互不干擾,在worker線程運(yùn)行結(jié)束后把結(jié)果返回給主線程。

    這樣做的好處是主線程可以把計(jì)算密集型或高延遲的任務(wù)交給worker線程執(zhí)行,這樣主線程就會(huì)變得輕松,不會(huì)被阻塞或拖慢。

    這并不意味著JS語言本身支持了多線程能力,而是瀏覽器作為宿主環(huán)境提供了JS一個(gè)多線程運(yùn)行的環(huán)境。

    不過因?yàn)閣orker一旦新建,就會(huì)一直運(yùn)行,不會(huì)被主線程的活動(dòng)打斷,這樣有利于隨時(shí)響應(yīng)主線程的通性,但是也會(huì)造成資源的浪費(fèi),所以不應(yīng)過度使用,用完注意關(guān)閉。

  3. 兼容性

    Browser IE Edge FireFox Chrome Safari
    version 10+ 12+ 3.5+ 4+ 4+
  4. 使用

    • 注意事項(xiàng):

      • 同源限制:分配給 Worker 線程運(yùn)行的腳本文件,必須與主線程的腳本文件同源。
      • DOM操作限制:Worker 線程所在的全局對(duì)象,與主線程不一樣,無法讀取主線程所在網(wǎng)頁的 DOM 對(duì)象。也不能獲取 document、 window等對(duì)象,但是可以獲取 navigator、 location(只讀)XMLHttpRequest、 setTimeout族等瀏覽器API。
      • 通信限制:Worker 線程和主線程不在同一個(gè)上下文環(huán)境,它們不能直接通信,必須通過消息完成。
      • 腳本限制:Worker 線程不能執(zhí)行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 對(duì)象發(fā)出 AJAX 請(qǐng)求。
      • 文件限制:Worker 線程無法讀取本地文件,即不能打開本機(jī)的文件系統(tǒng)(file://),它所加載的腳本,必須來自網(wǎng)絡(luò)。
    • 例子:

      • 主線程

        • 創(chuàng)建 Worker 線程:
        // 兩個(gè)參數(shù) 
        var myWorker = new Worker(jsUrl, options);
        

        Worker()構(gòu)造函數(shù),第一個(gè)參數(shù)是腳本的網(wǎng)址(必須遵守同源政策),該參數(shù)是必需的,且只能加載 JS 腳本,否則報(bào)錯(cuò)。第二個(gè)參數(shù)是配置對(duì)象,該對(duì)象可選。它的一個(gè)作用就是指定 Worker 的名稱,用來區(qū)分多個(gè) Worker 線程。

        • 主線程代碼示例:
        <!--主線程,HTML文件的body標(biāo)簽中-->
        <div>
            myworker 輸出內(nèi)容:
            <span id='app'></span>
            <input type='text' title='' id='msg'>
            <button onclick='sendMessage()'>
                    發(fā)送
            </button>
            <button onclick='stopWorker()'>
                  stop!
            </button>
        </div>
        <script type='text/javascript'>
          // 使用Worker前檢查一下瀏覽器是否支持
              if ( typeof ( Worker ) === 'undefined') 
                  document.writeln(' Sorry! No Web Worker support.. ')
              else {
                  // 創(chuàng)建 子線程 worker
                  let worker = new Worker('webWork.js', { name : 'worker' });
                  // 主線程 給 子線程 發(fā)消息
                  worker.postMessage('Hello World');
                  // worker.postMessage({method: 'echo', args: ['Work']});
        
                  // 主線程 監(jiān)聽 子線程 發(fā)的消息
                  worker.onmessage = function (event) {
                        console.log('Received message ' + event.data);
                        doSomething();
                  }
                  function doSomething() {
                        // 執(zhí)行任務(wù) 通知 子線程調(diào)用結(jié)束
                        worker.postMessage('Work done!');
                        // 使用 完畢 關(guān)閉 子線程
                        worker.terminate();
                  }
        
                  // 子線程 myworker
                  let myworker = new Worker('webWork2.js', { name : 'myworker'                   });
                // myworker 進(jìn)行計(jì)算 監(jiān)聽返回的結(jié)果 顯示在頁面上
                myworker.onmessage = (e) => {
                      document.getElementById("app").innerHTML = e.data
                }
        
                // 點(diǎn)擊停止 按鈕 通知 mywork 停止計(jì)算
                function stopWorker () {
                      myworker.postMessage('stop');
                      // myworker 完成任務(wù)以后,主線程就可以把它關(guān)掉。
                      myworker.terminate();
                }
        
          }
          </script>
        
      • Worker線程

        • Worker線程代碼示例:
          // webWork.js
        
          console.log(self.name,'webWork.js');
        
        // self代表子線程自身,即子線程的全局對(duì)象。
        
        // // 可以自己寫監(jiān)聽
        // self.addEventListener('message', function (e) {
        //   console.log(e.data,'data')
        //   self.postMessage('You said: ' + e.data);
        // }, false);
        
        // 也可以使用 onmessage 來進(jìn)行監(jiān)聽
        self.onmessage = (e) => {
            console.log(e.data,'e')
        self.postMessage('You said: ' + e.data);
        if (e.data == 'Work done!') {
          self.close();
          }
        }
        
         // webWork2.js
        
         console.log(self.name,'webWork2.js');
        
        // 定時(shí)器 計(jì)算值 返回給 主線程
        let i = 1;
        function add () {
         i ++;
         self.postMessage(i);
         setTimeout(add, 1000);
        }
        
        add();
        
        // 監(jiān)聽主線程消息
        self.onmessage = (e) => {
           if (e.data == 'stop') {
               self.close();
           }
        }
        
        
      • Worker 加載腳本

        • Worker 內(nèi)部如果要加載其他腳本,有一個(gè)專門的方importScripts()。
        // 引用單個(gè)腳本
        importScripts('script1.js');
          
        // 引用多個(gè)腳本
        importScripts('script1.js', 'script2.js');
        
      • 錯(cuò)誤處理

        • 主線程可以監(jiān)聽 Worker 是否發(fā)生錯(cuò)誤。如果發(fā)生錯(cuò)誤,Worker 會(huì)觸發(fā)主線程的error事件。
            worker.onerror(function (event) {
            console.log([
            'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
            ].join(''));
          });
          
          // 或者
          worker.addEventListener('error', function (event) {
              // ...
            });
          

        Worker 內(nèi)部也可以監(jiān)聽error事件。

      • 關(guān)閉 Worker

        • 使用完畢,為了節(jié)省系統(tǒng)資源,必須關(guān)閉 Worker。

          // 主線程
          worker.terminate();
          
          // Worker 線程
          self.close();
          
  5. 數(shù)據(jù)通信

    • 主線程與 Worker 之間的通信內(nèi)容,可以是文本,也可以是對(duì)象。需要注意的是,這種通信是拷貝關(guān)系,即是傳值而不是傳址,Worker 對(duì)通信內(nèi)容的修改,不會(huì)影響到主線程。事實(shí)上,瀏覽器內(nèi)部的運(yùn)行機(jī)制是,先將通信內(nèi)容串行化,然后把串行化后的字符串發(fā)給 Worker,后者再將它還原。

      主線程與 Worker 之間也可以交換二進(jìn)制數(shù)據(jù),比如 File、Blob、ArrayBuffer 等類型,也可以在線程之間發(fā)送。但是,拷貝方式發(fā)送二進(jìn)制數(shù)據(jù),會(huì)造成性能問題。比如,主線程向 Worker 發(fā)送一個(gè) 500MB 文件,默認(rèn)情況下瀏覽器會(huì)生成一個(gè)原文件的拷貝。為了解決這個(gè)問題,JavaScript 允許主線程把二進(jìn)制數(shù)據(jù)直接轉(zhuǎn)移給子線程,但是一旦轉(zhuǎn)移,主線程就無法再使用這些二進(jìn)制數(shù)據(jù)了,這是為了防止出現(xiàn)多個(gè)線程同時(shí)修改數(shù)據(jù)的麻煩局面。這種轉(zhuǎn)移數(shù)據(jù)的方法,叫做Transferable Objects。這使得主線程可以快速把數(shù)據(jù)交給 Worker,對(duì)于影像處理、聲音處理、3D 運(yùn)算等就非常方便了,不會(huì)產(chǎn)生性能負(fù)擔(dān)。

      • 如果要直接轉(zhuǎn)移數(shù)據(jù)的控制權(quán),就要使用下面的寫法。

        // Transferable Objects 格式
        worker.postMessage(arrayBuffer, [arrayBuffer]);
        
        // 例子
        var ab = new ArrayBuffer(1);
        worker.postMessage(ab, [ab]);
        
  6. 常用的API

    • 主線程中的API:
      • worker.postMessage: 主線程往worker線程發(fā)消息,消息可以是任意類型數(shù)據(jù),包括二進(jìn)制數(shù)據(jù)
      • worker.terminate: 主線程關(guān)閉worker線程
      • worker.onmessage: 指定worker線程發(fā)消息時(shí)的回調(diào),也可以通過worker.addEventListener('message',cb)的方式
      • worker.onerror: 指定worker線程發(fā)生錯(cuò)誤時(shí)的回調(diào),也可以worker.addEventListener('error',cb)
    • Worker線程中的API:
      • self.postMessage: worker線程往主線程發(fā)消息,消息可以是任意類型數(shù)據(jù),包括二進(jìn)制數(shù)據(jù)
      • self.close: worker線程關(guān)閉自己
      • self.onmessage: 指定主線程發(fā)worker線程消息時(shí)的回調(diào),也可以self.addEventListener('message',cb)
      • self.onerror: 指定worker線程發(fā)生錯(cuò)誤時(shí)的回調(diào),也可以 self.addEventListener('error',cb)
  7. 實(shí)戰(zhàn)場景

    • 輪詢

      有時(shí),瀏覽器需要輪詢服務(wù)器狀態(tài),以便第一時(shí)間得知狀態(tài)改變。這個(gè)工作可以放在 Worker 里面。

      function createWorker(f) {
        var blob = new Blob(['(' + f.toString() +')()']);
        var url = window.URL.createObjectURL(blob);
        var worker = new Worker(url);
        return worker;
      }
      
      var pollingWorker = createWorker(function (e) {
        var cache;
      
        function compare(new, old) { ... };
      
        setInterval(function () {
          fetch('/my-api-endpoint').then(function (res) {
            var data = res.json();
      
            if (!compare(data, cache)) {
              cache = data;
              self.postMessage(data);
            }
          })
        }, 1000)
      });
      
      pollingWorker.onmessage = function () {
        // render data
      }
      
      pollingWorker.postMessage('init');
      
  • 加密數(shù)據(jù)

    有些加解密的算法比較復(fù)雜,或者在加解密很多數(shù)據(jù)的時(shí)候,這會(huì)非常耗費(fèi)計(jì)算資源,導(dǎo)致UI線程無響應(yīng),因此這是使用Web Worker的好時(shí)機(jī),使用Worker線程可以讓用戶更加無縫的操作UI。

  • 預(yù)取數(shù)據(jù)

    有時(shí)候?yàn)榱颂嵘龜?shù)據(jù)加載速度,可以提前使用Worker線程獲取數(shù)據(jù),因?yàn)閃orker線程是可以是用 XMLHttpRequest 的。

  • 預(yù)渲染

    在某些渲染場景下,比如渲染復(fù)雜的canvas的時(shí)候需要計(jì)算的效果比如反射、折射、光影、材料等,這些計(jì)算的邏輯可以使用Worker線程來執(zhí)行,也可以使用多個(gè)Worker線程。

  • 復(fù)雜數(shù)據(jù)處理場景

    某些檢索、排序、過濾、分析會(huì)非常耗費(fèi)時(shí)間,這時(shí)可以使用Web Worker來進(jìn)行,不占用主線程。

  • 預(yù)加載圖片

    有時(shí)候一個(gè)頁面有很多圖片,或者有幾個(gè)很大的圖片的時(shí)候,如果業(yè)務(wù)限制不考慮懶加載,也可以使用Web Worker來加載圖片。

    // 主線程
    let w = new Worker("js/workers.js");
    w.onmessage =
      function (event) {
        var img = document.createElement("img");
        img.src = window.URL.createObjectURL(event.data);
        document.querySelector('#result').appendChild(img);
      };
    // worker線程
    let arr = [...圖片路徑];
    for (let i = 0, len = arr.length; i < len; i++) {
      let req = new XMLHttpRequest();
      req.open('GET', arr[i], true);
      req.responseType = "blob";
      req.setRequestHeader("client_type", "DESKTOP_WEB");
      req.onreadystatechange = () => {
        if (req.readyState == 4) {
          postMessage(req.response);
        }
      };
      req.send(null);
    }
    
  1. 注意事項(xiàng)

    • 雖然使用worker線程不會(huì)占用主線程,但是啟動(dòng)worker會(huì)比較耗費(fèi)資源

    • 主線程中使用XMLHttpRequest在請(qǐng)求過程中瀏覽器另開了一個(gè)異步http請(qǐng)求線程,但是交互過程中還是要消耗主線程資源

  2. 參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、概述 JavaScript 語言采用的是單線程模型,也就是說,所有任務(wù)只能在一個(gè)線程上完成,一次只能做一件事。...
    零星小雨_c84a閱讀 2,569評(píng)論 0 2
  • 作者:阮一峰www.ruanyifeng.com/blog/2018/07/web-worker.html 概述 ...
    grain先森閱讀 1,152評(píng)論 0 1
  • 在web開發(fā)中,前端是單線程操作的,當(dāng)前端有大運(yùn)算任務(wù)時(shí),前端會(huì)出現(xiàn)卡頓的情況,影響頁面性能。為了將任務(wù)在后臺(tái)操作...
    jshan閱讀 836評(píng)論 0 0
  • 「一」 沈若黎病了,很嚴(yán)重。 感冒發(fā)燒,好久都沒醒。 好了之后,也一直什么都不說,神情憂郁。 發(fā)生了什么?...
    戀愛小蘇打閱讀 677評(píng)論 0 8
  • 謝謝,已經(jīng)不需要了…… 無論是寬恕別人也好 還是讓自己解脫也罷 我都覺得絲毫沒有這個(gè)必要了 就像以后你再出現(xiàn) 可我...
    羽衣甘藍(lán)2025閱讀 152評(píng)論 0 0

友情鏈接更多精彩內(nèi)容