學習使用 Web Workers 在背景中運行 JavaScript 代碼的方法。

Introduction

JavaScript 是單線程的,無法同時並行運行。

這很好,因為我們不需要擔心並發編程中可能發生的一系列問題。

由於這個限制,JavaScript 代碼從一開始就被迫高效,否則用戶會有不好的體驗。昂貴的操作應該是異步的,以避免阻塞線程。

隨著 JavaScript 應用程序的需求越來越多,這在某些場景中開始成為一個問題。

Web Workers 在瀏覽器內引入了並行執行的可能性。

它們有幾個限制:

  • 無法訪問 DOM:Window 對象和 Document 對象
  • 它們可以通過消息與主 JavaScript 程序進行通信
  • 它們需要從相同的源頭(域名、端口和協議)加載
  • 如果使用文件協議(file://)提供頁面,它們就不能工作

Web Worker 的全局作用域是一個名為 WorkerGlobalScope 的對象,而不是在主線程中的 Window。

Browser support for Web Workers

非常好!

Web Workers 的瀏覽器支持

您可以使用以下代碼檢查是否支持 Web Workers:

if (typeof Worker !== 'undefined') {
}

Create a Web Worker

您可以通過初始化 Worker 對象並從相同的源頭加載 JavaScript 文件來創建一個 Web Worker:

const worker = new Worker('worker.js')

Communication with a Web Worker

有兩種主要的方法可以與 Web Worker 通信:

使用 Web Worker 物件中的 postMessage

您可以在 Worker 對象上使用 postMessage 發送訊息。

重要提示:訊息是傳輸的,而不是共享的。

main.js

const worker = new Worker('worker.js')
worker.postMessage('hello')

worker.js

onmessage = event => {
 console.log(event.data)
}

onerror = event => {
 console.error(event.message)
}

傳回訊息

Worker 可以通過使用其全局的 postMessage() 函數將訊息傳回到創建它的函數中。

worker.js

onmessage = event => {
 console.log(event.data)
 postMessage('hey')
}

onerror = event => {
 console.error(event.message)
}

main.js

const worker = new Worker('worker.js')
worker.postMessage('hello')

worker.onmessage = event => {
 console.log(event.data)
}

多個事件監聽器

如果您想為 message 事件設置多個監聽器,而不是使用 onmessage 創建一個事件監聽器(適用於 error 事件),則可以使用 addEventListener

worker.js

addEventListener('message', event => {
 console.log(event.data)
 postMessage('hey')
}, false)

addEventListener('message', event => {
 console.log(`I'm curious and I'm listening too`)
}, false)

addEventListener('error', event => {
 console.log(event.message)
}, false)

main.js

const worker = new Worker('worker.js')
worker.postMessage('hello')

worker.addEventListener('message', event => {
 console.log(event.data)
}, false)

使用 Channel Messaging API

與使用內建的 postMessage API 提供的 Web Workers 不同,我們可以選擇使用更通用的 Channel Messaging API 來進行通信。

main.js

const worker = new Worker('worker.js')
const messageChannel = new MessageChannel()
messageChannel.port1.addEventListener('message', event => {
 console.log(event.data)
})
worker.postMessage(data, [messageChannel.port2])

worker.js

addEventListener('message', event => {
 console.log(event.data)
})

Web Worker 可以通過將消息發送到 messageChannel.port2 來傳回訊息,例如:

addEventListener('message', event => {
 event.ports[0].postMessage(data)
})

Web Worker Lifecycle

如果 Web Worker 在 worker.onmessage 或添加事件監聽器後不保持在接聽模式下,那麼當其代碼執行完成時,它們將被關閉。

Web Worker 可以通過主線程的 terminate() 方法停止,在 Worker 內部可以使用全局方法 close()

main.js

const worker = new Worker('worker.js')
worker.postMessage('hello')
worker.terminate()

worker.js

worker.onmessage = event => {
 console.log(event.data)
 close()
}

worker.onerror = event => {
 console.error(event.message)
}

Loading libraries in a Web Worker

Web Worker 可以使用其全局作用域中定義的 importScripts() 全局函數:

importScripts('../utils/file.js', './something.js')

Web Worker 可用的 API

如前所述,Web Worker 無法訪問 DOM,因此無法與 windowdocument 對象進行交互,而 parent 也無法使用。

但是,您仍然可以使用許多其他 API,其中包括: