Web Worker 初探

之前咱們總說,JS是單線程沒有多線程,當JS在頁面中運行長耗時同步任務的時候就會致使頁面假死影響用戶體驗,從而須要設置把任務放在任務隊列中;執行任務隊列中的任務也並不是多線程進行的,然而如今HTML5提供了咱們前端開發這樣的能力 - Web Workers API,咱們一塊兒來看一看 Web Worker 是什麼,怎麼去使用它,在實際生產中如何去用它來進行產出。javascript

感興趣的同窗能夠加文末的微信羣,一塊兒討論吧~html

1. 概述

Web Workers 使得一個Web應用程序能夠在與主執行線程分離的後臺線程中運行一個腳本操做。這樣作的好處是能夠在一個單獨的線程中執行費時的處理任務,從而容許主(一般是UI)線程運行而不被阻塞。前端

它的做用就是給JS創造多線程運行環境,容許主線程建立worker線程,分配任務給後者,主線程運行的同時worker線程也在運行,相互不干擾,在worker線程運行結束後把結果返回給主線程。這樣作的好處是主線程能夠把計算密集型或高延遲的任務交給worker線程執行,這樣主線程就會變得輕鬆,不會被阻塞或拖慢。這並不意味着JS語言自己支持了多線程能力,而是瀏覽器做爲宿主環境提供了JS一個多線程運行的環境。java

不過由於worker一旦新建,就會一直運行,不會被主線程的活動打斷,這樣有利於隨時響應主線程的通性,可是也會形成資源的浪費,因此不該過分使用,用完注意關閉。或者說:若是worker無實例引用,該worker空閒後當即會被關閉;若是worker實列引用不爲0,該worker空閒也不會被關閉。web

看一看它的兼容性ajax

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

2. 使用

2.1 限制

worker線程的使用有一些注意點算法

  1. 同源限制 worker線程執行的腳本文件必須和主線程的腳本文件同源,這是固然的了,總不能容許worker線程到別人電腦上處處讀文件吧
  2. 文件限制 爲了安全,worker線程沒法讀取本地文件,它所加載的腳本必須來自網絡,且須要與主線程的腳本同源
  3. DOM操做限制 worker線程在與主線程的window不一樣的另外一個全局上下文中運行,其中沒法讀取主線程所在網頁的DOM對象,也不能獲取 documentwindow等對象,可是能夠獲取navigatorlocation(只讀)XMLHttpRequestsetTimeout族等瀏覽器API。
  4. 通訊限制 worker線程與主線程不在同一個上下文,不能直接通訊,須要經過postMessage方法來通訊。
  5. 腳本限制 worker線程不能執行alertconfirm,但可使用 XMLHttpRequest 對象發出ajax請求。

2.2 例子

在主線程中生成 Worker 線程很容易:canvas

var myWorker = new Worker(jsUrl, options)
複製代碼

Worker()構造函數,第一個參數是腳本的網址(必須遵照同源政策),該參數是必需的,且只能加載 JS 腳本,不然報錯。第二個參數是配置對象,該對象可選。它的一個做用就是指定 Worker 的名稱,用來區分多個 Worker 線程。segmentfault

// 主線程

var myWorker = new Worker('worker.js', { name : 'myWorker' });

// Worker 線程
self.name // myWorker

複製代碼

關於api什麼的,直接上例子大概就能明白了,首先是worker線程的js文件:api

// workerThread1.js

let i = 1

function simpleCount() {
  i++
  self.postMessage(i)
  setTimeout(simpleCount, 1000)
}

simpleCount()

self.onmessage = ev => {
  postMessage(ev.data + ' 呵呵~')
}
複製代碼

在HTML文件中的body中:

<!--主線程,HTML文件的body標籤中-->

<div>
  Worker 輸出內容:<span id='app'></span>
  <input type='text' title='' id='msg'>
  <button onclick='sendMessage()'>發送</button>
  <button onclick='stopWorker()'>stop!</button>
</div>

<script type='text/javascript'> if (typeof(Worker) === 'undefined') // 使用Worker前檢查一下瀏覽器是否支持 document.writeln(' Sorry! No Web Worker support.. ') else { window.w = new Worker('workerThread1.js') window.w.onmessage = ev => { document.getElementById('app').innerHTML = ev.data } window.w.onerror = err => { w.terminate() console.log(error.filename, error.lineno, error.message) // 發生錯誤的文件名、行號、錯誤內容 } function sendMessage() { const msg = document.getElementById('msg') window.w.postMessage(msg.value) } function stopWorker() { window.w.terminate() } } </script>
複製代碼

能夠本身運行一下看看效果,上面用到了一些經常使用的api

主線程中的api,worker表示是 Worker 的實例:

  • worker.postMessage: 主線程往worker線程發消息,消息能夠是任意類型數據,包括二進制數據
  • worker.terminate: 主線程關閉worker線程
  • worker.onmessage: 指定worker線程發消息時的回調,也能夠經過worker.addEventListener('message',cb)的方式
  • worker.onerror: 指定worker線程發生錯誤時的回調,也能夠 worker.addEventListener('error',cb)

Worker線程中全局對象爲 self,表明子線程自身,這時 this指向self,其上有一些api:

  • self.postMessage: worker線程往主線程發消息,消息能夠是任意類型數據,包括二進制數據
  • self.close: worker線程關閉本身
  • self.onmessage: 指定主線程發worker線程消息時的回調,也能夠self.addEventListener('message',cb)
  • self.onerror: 指定worker線程發生錯誤時的回調,也能夠 self.addEventListener('error',cb)

注意,w.postMessage(aMessage, transferList) 方法接受兩個參數,aMessage 是能夠傳遞任何類型數據的,包括對象,這種通訊是拷貝關係,便是傳值而不是傳址,Worker 對通訊內容的修改,不會影響到主線程。事實上,瀏覽器內部的運行機制是,先將通訊內容串行化,而後把串行化後的字符串發給 Worker,後者再將它還原。一個可選的 Transferable對象的數組,用於傳遞全部權。若是一個對象的全部權被轉移,在發送它的上下文中將變爲不可用(停止),而且只有在它被髮送到的worker中可用。可轉移對象是如ArrayBuffer,MessagePort或ImageBitmap的實例對象,transferList數組中不可傳入null。

更詳細的API參見 MDN - WorkerGlobalScope

worker線程中加載腳本的api:

importScripts('script1.js')	// 加載單個腳本
importScripts('script1.js', 'script2.js')	// 加載多個腳本
複製代碼

3. 實戰場景

我的以爲,Web Worker咱們能夠當作計算器來用,須要用的時候掏出來摁一摁,不用的時候必定要收起來~

  1. 加密數據 有些加解密的算法比較複雜,或者在加解密不少數據的時候,這會很是耗費計算資源,致使UI線程無響應,所以這是使用Web Worker的好時機,使用Worker線程可讓用戶更加無縫的操做UI。

  2. 預取數據 有時候爲了提高數據加載速度,能夠提早使用Worker線程獲取數據,由於Worker線程是能夠是用 XMLHttpRequest 的。

  3. 預渲染 在某些渲染場景下,好比渲染複雜的canvas的時候須要計算的效果好比反射、折射、光影、材料等,這些計算的邏輯可使用Worker線程來執行,也可使用多個Worker線程,這裏有個射線追蹤的示例

  4. 複雜數據處理場景 某些檢索、排序、過濾、分析會很是耗費時間,這時可使用Web Worker來進行,不佔用主線程。

  5. 預加載圖片 有時候一個頁面有不少圖片,或者有幾個很大的圖片的時候,若是業務限制不考慮懶加載,也可使用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);
    }
    複製代碼

在實戰的時候注意

  • 雖然使用worker線程不會佔用主線程,可是啓動worker會比較耗費資源
  • 主線程中使用XMLHttpRequest在請求過程當中瀏覽器另開了一個異步http請求線程,可是交互過程當中仍是要消耗主線程資源

在 Webpack 項目裏面使用 Web Worker 請參照:怎麼在 ES6+Webpack 下使用 Web Worker

至於還有Shared Worker、Service Worker什麼的,咱們就不看了,IE不喜歡


網上的帖子大多深淺不一,甚至有些先後矛盾,在下的文章都是學習過程當中的總結,若是發現錯誤,歡迎留言指出~

參考:

  1. MDN - Web Workers 概念與用法
  2. 阮一峯 - Web Worker 使用教程
  3. JavaScript 工做原理之七-Web Workers 分類及 5 個使用場景
  4. Web Worker在項目中的妙用
  5. 怎麼在 ES6+Webpack 下使用 Web Worker

PS:歡迎你們關注個人公衆號【前端下午茶】,一塊兒加油吧~

另外能夠加入「前端下午茶交流羣」微信羣,長按識別下面二維碼便可加我好友,備註加羣,我拉你入羣~

相關文章
相關標籤/搜索