web worker 實現瀏覽器多線程

JS 自然屬於單線程環境,也就是說不能同時運行多個腳本。爲什麼這樣?由於JS的設計目標是實現網頁和用戶交互,試想當用戶點擊頁面,操做 DOM 時,有兩個線程同時操做了DOM,那麼以誰的結果爲準呢?最壞的狀況一個線程須要給一個元素添加內容,另外一個線程卻把元素刪除了,這將致使用戶沒法和頁面交互。這就是JS只能單線程的緣由,全部任務都在一個線程上執行,沒必要考慮多線程的問題。JS 須要同時執行兩個任務的地方,可經過定時器、事件處理器等異步技術實現並行(其實依然單線程)。HTML5 引入 web worker 實現真正的多線程。經過 Web Worker 在後臺執行一些操做,例如觸發長時間運行的腳本以處理計算密集型任務,同時卻不會阻礙 UI 或其餘腳本處理用戶互動。 Worker 利用相似線程的消息傳遞實現並行。這很是適合您確保對 UI 的刷新、性能以及對用戶的響應。javascript

web worker 的類型

兩種類型:html

  • 專用 worker(Dedicated workers):只能在建立它的腳本中使用;
  • 共享 worker(Shared workers):可在多個腳本中使用。

worker 和主線程之間通訊經過消息機制進行--使用postMessage 函數向對方傳遞數據,對方經過監聽message事件獲取並處理數據。數據不是共享,而是複製。因此傳遞對象時,能夠操做該對象,而不會影響另外一個線程中的對象。html5

worker 和主線程的腳本必須同源,簡單理解成同一個網站加載到瀏覽中。java

專用 worker

worker 特性檢測

在主線程檢測瀏覽器是否支持 worker,在決定是否建立 worker。web

if(window.worker){
  //do something
}
複製代碼

目前主要瀏覽器都支持。更多詳情數據庫

使用入門

web worker 在獨立線程中運行,可將其代碼保存在一個單獨的JS文件中,而後在建立 worker 時引入該腳本。不少瀏覽器還不支持本地文件運行worker,咱們須要結合服務器時間。可以使用 express 建立一個服務器。express

public/javascripts 文件夾在建立 worker.js:canvas

this.addEventListener('message', (event) => {
  console.log(this)
  console.log(event)
  console.log(event.data)
  console.log(event.origin)
  console.log(event.ports)
  console.log(event.source)
  console.log(event.lastEventId)
  self.postMessage({ name: 'worker.js', data: 'hello,main.js!' })
}, false)
複製代碼

這就是 worker 線程的代碼。api

在頁面的 JS 代碼中建立 worker,爲了簡單起見,在script 標籤中寫腳本:數組

<script> let worker = new Worker('/javascripts/worker.js');//建立一個 worker let workerButton = document.getElementByI('workerButton'); workerButton.addEventListener('click', (event)=> { console.log(event) worker.postMessage({ name: 'main.js', data:'hello,worker.js!' });//向 worker 線程發送消息 }, false) // 監聽 worker 線程發送過來的消息 worker.addEventListener('message', (event) => { console.log(event.data); }, false) </script>
複製代碼

建立 worker 時指定的腳本是異步加載的,若是加載成功,會生成一個 worker 線程。徹底加載和執行以前,系統很差生成 worker。若是腳本不存在,返回404,建立 worker失敗。

postMessage 用來啓動 worker,也用來發送消息。

中止 worker

在主線程中,經過worker.terminate() 中止 worker;在worker 線程中,使用close()來中止worker。

let stopWorker = document.getElementById('stopWorker');
stopWorker.addEventListener('click', () => {
  let result = worker.terminate();
  console.log(result);
}, false);
複製代碼

worker 本身中止:

在主線程中通知worker中止:

let stopWorker = document.getElementById('stopWorker');
stopWorker.addEventListener('click', () => {
  let result = worker.postMessage('stop');
  console.log(result);//undefined
}, false)
複製代碼

worker.js

this.addEventListener('message', (event) => {
  console.log(this)
  console.log(event)
  console.log(event.data)
  console.log(event.origin)
  console.log('cache',this.cache)
  console.log(event.source)
  console.log(event.lastEventId)
  if ('stop' === event.data) {
    let result = this.close();
    console.log('worker stop ',result);//undefined
  }
  self.postMessage({ name: 'worker.js', data: 'hello,main.js!' })
}, false)
複製代碼

worker 環境

worker 做用域:在worker腳本文件內,this 和 self 都是全局做用域。上面的console.log(this)的輸出是:

DedicatedWorkerGlobalScope。
複製代碼

worker 線程中並不能完成使用主線程的功能,只能使用部分JS功能:

  • navigator 對象;
  • location 對象;
  • XMLHttpRequest 對象;
  • 定時器;
  • 應用緩存;
  • importScripts();
  • 生成其餘 worker。

worker 中沒法使用:

  • DOM(非線程安全);
  • window 對象;
  • document 對象;
  • parent 對象。

咱們在 worker.js 發發送一個http 請求,拿到返回值後傳遞給主線程:

複製代碼

更多詳細狀況

加載外 worker 腳本

可以使用importScripts 函數加載外部的腳本進入 worker 腳本執行。

在 worker.js 記載一個腳本:

importScripts('./importTest.js');// 可傳遞多個路徑,路徑是相對於 worker.js 的
複製代碼

importTest.js

test(5)
function test(time) {
  setInterval(() => {
    console.log('你好');
  }, 1000 * time);
複製代碼

worker 線程中止後,加載的外部腳本也中止執行。

錯誤處理

可在主線程中處理 worker 線程的錯誤,在主線程中監聽 worker 的 錯誤事件:

worker.addEventListener('error', (event) => {
  console.log(event.colno);
  console.log(event.filename);
  console.log(event.message);
}, false);
複製代碼

輸出該錯誤事件,可看到事件的一些屬性: 不冒泡、可取消等,具備普通事件的一些屬性。

worker 生成一個錯誤:

this.addEventListener('message', (event) => {
  if ('stop' === event.data) {
    let result = this.close();
    console.log('worker stop ', result);
  }
  self.postMessage({ name: 'worker.js', data: 'hello,main.js!' })
  this.postMessage(new Error('製造一個錯誤!'));
}, false)
複製代碼

內嵌 worker

上面的例子,將 worker 代碼放在單獨的文件裏,這樣便於管理代碼,也方便修改,由於有語法高亮,能夠將 worker 代碼放在和主線程相關的html文件裏:

<script id="worker" type="javascript/worker"> this.addEventListener('message', (event) => { if ('stop' === event.data) { this.close(); } console.log(event.data); self.postMessage({ name: 'worker.js', data: 'hello,main.js!' }); }, false); </script>
  <script> let workerContent = document.getElementById('worker').textContent; let blob = new Blob([workerContent], { type: 'text/javacript' }); let url = URL.createObjectURL(blob); console.log(url); let worker = new Worker(url); worker.addEventListener('message', (event) => { console.log(event.data); }); let workerButton = document.querySelector('#workerButton'); workerButton.addEventListener('click', (event) => { worker.postMessage({ name: "主線程" }); }, false); </script>
複製代碼

將 worker 的代碼在 script 標籤中,聲明類型爲javascript/worker,就不會被瀏覽器解析成JS代碼,而是當成普通的 html 標籤,可經過DOM api 獲取標籤內的文本,建立 worker。 使用 Blob 生成一個blob 對象,再使用 URL.createObjectURL成鏈接,再生成 worker。

let blob = new Blob([workerContent], { type: 'text/javacript' });
let url = URL.createObjectURL(blob);
console.log(url);
let worker = new Worker(url);
複製代碼

關於Blob 關於Blob和URL

worker 的使用場景

經常使用的場景:

  • 預先抓取和/或緩存數據以便稍後使用;
  • 突出顯示代碼語法或其餘實時文本格式;
  • 拼寫檢查程序;
  • 分析視頻或音頻數據;
  • 背景 I/O 或網絡服務輪詢;
  • 處理較大數組或超大 JSON 響應;
  • <canvas> 中的圖片過濾;
  • 更新本地網絡數據庫中的多行內容。

更多參考

Web Workers 的基本信息

使用 Web Workers

worker讀取文件

相關文章
相關標籤/搜索