使用 Web Workers

Web Workers 是一種簡單的方式,允許 web 內容在後臺執行緒中執行指令碼。worker 執行緒可以在不干擾使用者介面的情況下執行任務。此外,它們可以使用 fetch()XMLHttpRequest API 傳送網路請求。建立後,worker 可以透過將訊息釋出到由建立它的 JavaScript 程式碼指定的事件處理程式(反之亦然)來向該程式碼傳送訊息。

本文詳細介紹了 Web Workers 的使用。

Web Workers API

worker 是使用建構函式(例如 Worker())建立的物件,它執行一個命名的 JavaScript 檔案 — 此檔案包含將在 worker 執行緒中執行的程式碼;worker 在與當前 window 不同的另一個全域性上下文中執行。因此,在 Worker 中使用 window 快捷方式獲取當前全域性作用域(而不是 self)將返回錯誤。

在專用 worker(由單個指令碼使用的標準 worker;共享 worker 使用 SharedWorkerGlobalScope)的情況下,worker 上下文由 DedicatedWorkerGlobalScope 物件表示。專用 worker 只能從首次生成它的指令碼訪問,而共享 worker 可以從多個指令碼訪問。

注意: 有關 worker 的參考文件和附加指南,請參閱 Web Workers API 登陸頁面

你可以在 worker 執行緒中執行任何你喜歡的程式碼,但有一些例外。例如,你不能直接從 worker 內部操作 DOM,也不能使用 window 物件的某些預設方法和屬性。但是,你可以使用 window 下的許多可用項,包括 WebSocketsIndexedDB 等資料儲存機制。有關更多詳細資訊,請參閱 worker 可用的函式和類

資料透過訊息系統在 worker 和主執行緒之間傳送 — 雙方都使用 postMessage() 方法傳送訊息,並透過 onmessage 事件處理程式響應訊息(訊息包含在 message 事件的 data 屬性中)。資料是複製而不是共享的。

worker 反過來也可以生成新的 worker,只要這些 worker 與父頁面位於相同的 來源

此外,worker 可以使用 fetch()XMLHttpRequest API 傳送網路請求(但請注意,XMLHttpRequestresponseXML 屬性將始終為 null)。

專用 worker

如上所述,專用 worker 只能由呼叫它的指令碼訪問。在本節中,我們將討論我們的 基本專用 worker 示例 中找到的 JavaScript(執行專用 worker):這允許你輸入兩個數字進行乘法運算。這些數字被髮送到專用 worker,進行乘法運算,結果返回到頁面並顯示。

這個例子相當簡單,但我們決定在向你介紹基本 worker 概念時保持簡單。更高階的細節將在文章後面介紹。

worker 功能檢測

為了稍微更受控制的錯誤處理和向後相容性,最好將你的 worker 訪問程式碼包裝在以下內容中(main.js

js
if (window.Worker) {
  // …
}

生成專用 worker

建立一個新 worker 很簡單。你所需要做的就是呼叫 Worker() 建構函式,指定要在 worker 執行緒中執行的指令碼的 URI(main.js

js
const myWorker = new Worker("worker.js");

注意: 包括 webpackViteParcel 在內的打包器建議將相對於 import.meta.url 解析的 URL 傳遞給 Worker() 建構函式。例如

js
const myWorker = new Worker(new URL("worker.js", import.meta.url));

這樣,路徑是相對於當前指令碼而不是當前 HTML 頁面,這允許打包器安全地進行最佳化,例如重新命名(因為否則 worker.js URL 可能指向不受打包器控制的檔案,因此它不能做任何假設)。

向專用 worker 傳送和接收訊息

worker 的魔力透過 postMessage() 方法和 onmessage 事件處理程式發生。當你想向 worker 傳送訊息時,你可以像這樣向它釋出訊息(main.js

js
[first, second].forEach((input) => {
  input.onchange = () => {
    myWorker.postMessage([first.value, second.value]);
    console.log("Message posted to worker");
  };
});

所以這裡我們有兩個由變數 firstsecond 表示的 <input> 元素;當其中任何一個的值發生變化時,myWorker.postMessage([first.value,second.value]) 用於將兩者中的值作為陣列傳送到 worker。你可以在訊息中傳送你喜歡的任何內容。

在 worker 中,我們可以透過編寫如下事件處理程式塊來響應接收到的訊息(worker.js

js
onmessage = (e) => {
  console.log("Message received from main script");
  const workerResult = `Result: ${e.data[0] * e.data[1]}`;
  console.log("Posting message back to main script");
  postMessage(workerResult);
};

onmessage 處理程式允許我們在收到訊息時執行一些程式碼,訊息本身在 message 事件的 data 屬性中可用。這裡我們將兩個數字相乘,然後再次使用 postMessage(),將結果釋出回主執行緒。

回到主執行緒,我們再次使用 onmessage 來響應從 worker 傳送回來的訊息

js
myWorker.onmessage = (e) => {
  result.textContent = e.data;
  console.log("Message received from worker");
};

這裡我們獲取訊息事件資料並將其設定為結果段落的 textContent,以便使用者可以看到計算結果。

注意: 請注意,在主指令碼執行緒中使用 onmessagepostMessage() 時,它們需要掛在 Worker 物件上,但在 worker 中使用時則不需要。這是因為,在 worker 內部,worker 實際上就是全域性作用域。

注意: 當訊息在主執行緒和 worker 之間傳遞時,它是複製或“轉移”(移動),而不是共享。閱讀 在 worker 之間傳輸資料:更多詳情 以獲得更徹底的解釋。

終止 worker

如果你需要立即從主執行緒終止正在執行的 worker,你可以透過呼叫 worker 的 terminate 方法來實現

js
myWorker.terminate();

worker 執行緒立即被終止。

處理錯誤

當 worker 中發生執行時錯誤時,它的 onerror 事件處理程式會被呼叫。它接收一個名為 error 的事件,該事件實現了 ErrorEvent 介面。

事件不冒泡且可取消;為了防止預設操作發生,worker 可以呼叫錯誤事件的 preventDefault() 方法。

錯誤事件具有以下三個值得關注的欄位

message

人類可讀的錯誤訊息。

檔名

發生錯誤的指令碼檔案的名稱。

行號

發生錯誤的指令碼檔案的行號。

生成子 worker

worker 可以根據需要生成更多的 worker。所謂的子 worker 必須與父頁面位於相同的來源。此外,子 worker 的 URI 是相對於父 worker 的位置解析的,而不是擁有頁面的位置。這使得 worker 更容易跟蹤其依賴項的位置。

匯入指令碼和庫

worker 執行緒可以訪問一個全域性函式 importScripts(),它允許它們匯入指令碼。它接受零個或多個 URI 作為要匯入的資源的引數;以下所有示例都有效

js
importScripts(); /* imports nothing */
importScripts("foo.js"); /* imports just "foo.js" */
importScripts("foo.js", "bar.js"); /* imports two scripts */
importScripts(
  "//example.com/hello.js",
); /* You can import scripts from other origins */

瀏覽器載入每個列出的指令碼並執行它。然後 worker 可以使用每個指令碼中的任何全域性物件。如果指令碼無法載入,則會丟擲 NETWORK_ERROR,後續程式碼將不會執行。但是,之前執行的程式碼(包括使用 setTimeout() 延遲的程式碼)仍然可以正常工作。importScripts() 方法之後的函式宣告也會保留,因為它們總是在其餘程式碼之前進行評估。

注意: 指令碼可能以任何順序下載,但將按照你將檔名傳遞給 importScripts() 的順序執行。這是同步完成的;importScripts() 在所有指令碼載入和執行完畢之前不會返回。

共享 worker

共享 worker 可以被多個指令碼訪問 — 即使它們被不同的視窗、iframe 甚至 worker 訪問。在本節中,我們將討論我們的 基本共享 worker 示例 中找到的 JavaScript(執行共享 worker):這與基本專用 worker 示例非常相似,不同之處在於它有兩個由不同指令碼檔案處理的功能:將兩個數字相乘將一個數字平方。這兩個指令碼都使用相同的 worker 來執行所需的實際計算。

這裡我們將重點關注專用 worker 和共享 worker 之間的差異。請注意,在此示例中,我們有兩個 HTML 頁面,每個頁面都應用了使用相同單個 worker 檔案的 JavaScript。

注意: 如果 SharedWorker 可以從多個瀏覽上下文訪問,則所有這些瀏覽上下文必須共享完全相同的來源(相同的協議、主機和埠)。

注意: 在 Firefox 中,共享 worker 不能在私有視窗和非私有視窗載入的文件之間共享(Firefox bug 1177621)。

生成共享 worker

生成一個新的共享 worker 與專用 worker 幾乎相同,但建構函式名稱不同(請參閱 index.htmlindex2.html)— 每個都必須使用以下程式碼啟動 worker

js
const myWorker = new SharedWorker("worker.js");

一個很大的區別是,使用共享 worker,你必須透過 port 物件進行通訊 — 會開啟一個顯式埠,指令碼可以使用它與 worker 進行通訊(在專用 worker 的情況下,這是隱式完成的)。

在釋出任何訊息之前,埠連線需要透過使用 onmessage 事件處理程式隱式啟動,或者透過 start() 方法顯式啟動。只有當 message 事件透過 addEventListener() 方法連線時才需要呼叫 start()

注意: 當使用 start() 方法開啟埠連線時,如果需要雙向通訊,則需要從父執行緒和 worker 執行緒都呼叫它。

向共享 worker 傳送和接收訊息

現在可以像以前一樣向 worker 傳送訊息,但 postMessage() 方法必須透過埠物件呼叫(同樣,你會在 multiply.jssquare.js 中看到類似的構造)

js
squareNumber.onchange = () => {
  myWorker.port.postMessage([squareNumber.value, squareNumber.value]);
  console.log("Message posted to worker");
};

現在,我們來看看 worker。這裡也有些複雜(worker.js

js
onconnect = (e) => {
  const port = e.ports[0];

  port.onmessage = (e) => {
    const workerResult = `Result: ${e.data[0] * e.data[1]}`;
    port.postMessage(workerResult);
  };
};

首先,我們使用 onconnect 處理程式在埠連線發生時觸發程式碼(即,當父執行緒中的 onmessage 事件處理程式設定好時,或者當 start() 方法在父執行緒中顯式呼叫時)。

我們使用此事件物件的 ports 屬性來獲取埠並將其儲存在變數中。

接下來,我們在埠上新增一個 onmessage 處理程式來執行計算並將結果返回到主執行緒。在 worker 執行緒中設定此 onmessage 處理程式也隱式打開了回父執行緒的埠連線,因此實際上不需要呼叫 port.start(),如上所述。

最後,回到主指令碼中,我們處理訊息(同樣,你會在 multiply.jssquare.js 中看到類似的構造)

js
myWorker.port.onmessage = (e) => {
  result2.textContent = e.data;
  console.log("Message received from worker");
};

當訊息透過埠從 worker 返回時,我們將計算結果插入到相應的段落中。

關於執行緒安全

Worker 介面生成真實的 OS 級執行緒,細心的程式設計師可能會擔心併發如果處理不當,會在你的程式碼中引起“有趣”的效果。

然而,由於 web worker 與其他執行緒之間的通訊點受到嚴格控制,實際上很難導致併發問題。無法訪問非執行緒安全元件或 DOM。你必須透過序列化物件線上程之間傳遞特定資料。因此,你必須非常努力才能在程式碼中引起問題。

內容安全策略

worker 被認為擁有自己的執行上下文,與建立它們的文件不同。因此,它們通常不受建立它們的文件(或父 worker)的 內容安全策略 的約束。例如,假設一個文件具有以下標頭

http
Content-Security-Policy: script-src 'self'

除其他外,這將阻止其包含的任何指令碼使用 eval()。但是,如果指令碼構造了一個 worker,則在 worker 上下文中執行的程式碼被允許使用 eval()

要為 worker 指定內容安全策略,請為傳遞 worker 指令碼本身的請求設定 Content-Security-Policy 響應頭。

此規則的例外是 worker 指令碼的來源是全域性唯一識別符號(例如,如果其 URL 具有資料或 blob 方案)。在這種情況下,worker 將繼承建立它的文件或 worker 的 CSP。

在 worker 之間傳輸資料:更多詳情

主頁面和 worker 之間傳遞的資料是複製的,而不是共享的(除了某些可以顯式共享的物件)。物件在傳遞給 worker 時被序列化,然後在另一端反序列化。頁面和 worker 不共享相同的例項,因此最終結果是在兩端建立了副本。大多數瀏覽器都將此功能實現為結構化克隆

正如你現在可能已經知道的,資料透過 postMessage() 使用訊息在兩個執行緒之間交換,message 事件的 data 屬性包含從 worker 返回的資料。

example.html:(主頁面)

js
const myWorker = new Worker("my_task.js");

myWorker.onmessage = (event) => {
  console.log(`Worker said : "${event.data}"`);
};

myWorker.postMessage({ lastUpdate: new Date() });

my_task.js (worker)

js
self.onmessage = (event) => {
  postMessage(`Last updated: ${event.data.lastUpdate.toDateString()}`);
};

結構化克隆 演算法可以接受 JSON 和一些 JSON 無法接受的東西——比如迴圈引用。

傳遞資料示例

示例 1:高階傳遞 JSON 資料並建立切換系統

如果你必須傳遞一些複雜的資料並且必須在主頁面和 Worker 中呼叫許多不同的函式,你可以建立一個將所有內容組合在一起的系統。

首先,我們建立一個 QueryableWorker 類,它接受 worker 的 URL、一個預設監聽器和一個錯誤處理程式,這個類將跟蹤監聽器列表並幫助我們與 worker 進行通訊

js
function QueryableWorker(url, defaultListener, onError) {
  const worker = new Worker(url);
  const listeners = {};

  this.defaultListener = defaultListener ?? (() => {});

  if (onError) {
    worker.onerror = onError;
  }

  this.postMessage = (message) => {
    worker.postMessage(message);
  };

  this.terminate = () => {
    worker.terminate();
  };
}

然後我們新增新增/刪除監聽器的方法

js
this.addListeners = (name, listener) => {
  listeners[name] = listener;
};

this.removeListeners = (name) => {
  delete listeners[name];
};

這裡我們讓 worker 處理兩個簡單的操作進行說明:獲取兩個數字的差值並在三秒後發出警報。為了實現這一點,我們首先實現一個 sendQuery 方法,該方法查詢 worker 是否實際具有執行我們想要的操作的相應方法。

js
// This functions takes at least one argument, the method name we want to query.
// Then we can pass in the arguments that the method needs.
this.sendQuery = (queryMethod, ...queryMethodArguments) => {
  if (!queryMethod) {
    throw new TypeError(
      "QueryableWorker.sendQuery takes at least one argument",
    );
  }
  worker.postMessage({
    queryMethod,
    queryMethodArguments,
  });
};

我們用 onmessage 方法完成 QueryableWorker。如果 worker 具有我們查詢的相應方法,它應該返回相應監聽器的名稱和它需要的引數,我們只需在 listeners 中找到它。

js
worker.onmessage = (event) => {
  if (
    event.data instanceof Object &&
    Object.hasOwn(event.data, "queryMethodListener") &&
    Object.hasOwn(event.data, "queryMethodArguments")
  ) {
    listeners[event.data.queryMethodListener].apply(
      this,
      event.data.queryMethodArguments,
    );
  } else {
    this.defaultListener(event.data);
  }
};

現在來看 worker。首先我們需要有處理兩個簡單操作的方法

js
const queryableFunctions = {
  getDifference(a, b) {
    reply("printStuff", a - b);
  },
  waitSomeTime() {
    setTimeout(() => {
      reply("doAlert", 3, "seconds");
    }, 3000);
  },
};

function reply(queryMethodListener, ...queryMethodArguments) {
  if (!queryMethodListener) {
    throw new TypeError("reply - takes at least one argument");
  }
  postMessage({
    queryMethodListener,
    queryMethodArguments,
  });
}

// This method is called when main page calls QueryWorker's postMessage
// method directly
function defaultReply(message) {
  // do something
}

現在 onmessage 方法變得很簡單

js
onmessage = (event) => {
  if (
    event.data instanceof Object &&
    Object.hasOwn(event.data, "queryMethod") &&
    Object.hasOwn(event.data, "queryMethodArguments")
  ) {
    queryableFunctions[event.data.queryMethod].apply(
      self,
      event.data.queryMethodArguments,
    );
  } else {
    defaultReply(event.data);
  }
};

這是完整的實現

example.html (主頁面)

html
<ul>
  <li>
    <button id="first-action">What is the difference between 5 and 3?</button>
  </li>
  <li>
    <button id="second-action">Wait 3 seconds</button>
  </li>
  <li>
    <button id="terminate">terminate() the Worker</button>
  </li>
</ul>

它需要執行以下指令碼,無論是內聯還是作為外部檔案

js
// QueryableWorker instances methods:
//   * sendQuery(queryable function name, argument to pass 1, argument to pass 2, etc. etc.): calls a Worker's queryable function
//   * postMessage(string or JSON Data): see Worker.prototype.postMessage()
//   * terminate(): terminates the Worker
//   * addListener(name, function): adds a listener
//   * removeListener(name): removes a listener
// QueryableWorker instances properties:
//   * defaultListener: the default listener executed only when the Worker calls the postMessage() function directly
function QueryableWorker(url, defaultListener, onError) {
  const worker = new Worker(url);
  const listeners = {};

  this.defaultListener = defaultListener ?? (() => {});

  if (onError) {
    worker.onerror = onError;
  }

  this.postMessage = (message) => {
    worker.postMessage(message);
  };

  this.terminate = () => {
    worker.terminate();
  };

  this.addListener = (name, listener) => {
    listeners[name] = listener;
  };

  this.removeListener = (name) => {
    delete listeners[name];
  };

  // This functions takes at least one argument, the method name we want to query.
  // Then we can pass in the arguments that the method needs.
  this.sendQuery = (queryMethod, ...queryMethodArguments) => {
    if (!queryMethod) {
      throw new TypeError(
        "QueryableWorker.sendQuery takes at least one argument",
      );
    }
    worker.postMessage({
      queryMethod,
      queryMethodArguments,
    });
  };

  worker.onmessage = (event) => {
    if (
      event.data instanceof Object &&
      Object.hasOwn(event.data, "queryMethodListener") &&
      Object.hasOwn(event.data, "queryMethodArguments")
    ) {
      listeners[event.data.queryMethodListener].apply(
        this,
        event.data.queryMethodArguments,
      );
    } else {
      this.defaultListener(event.data);
    }
  };
}

// your custom "queryable" worker
const myTask = new QueryableWorker("my_task.js");

// your custom "listeners"
myTask.addListener("printStuff", (result) => {
  document
    .getElementById("firstLink")
    .parentNode.appendChild(
      document.createTextNode(`The difference is ${result}!`),
    );
});

myTask.addListener("doAlert", (time, unit) => {
  alert(`Worker waited for ${time} ${unit} :-)`);
});

document.getElementById("first-action").addEventListener("click", () => {
  myTask.sendQuery("getDifference", 5, 3);
});
document.getElementById("second-action").addEventListener("click", () => {
  myTask.sendQuery("waitSomeTime");
});
document.getElementById("terminate").addEventListener("click", () => {
  myTask.terminate();
});

my_task.js (worker)

js
const queryableFunctions = {
  // example #1: get the difference between two numbers:
  getDifference(minuend, subtrahend) {
    reply("printStuff", minuend - subtrahend);
  },

  // example #2: wait three seconds
  waitSomeTime() {
    setTimeout(() => {
      reply("doAlert", 3, "seconds");
    }, 3000);
  },
};

// system functions

function defaultReply(message) {
  // your default PUBLIC function executed only when main page calls the queryableWorker.postMessage() method directly
  // do something
}

function reply(queryMethodListener, ...queryMethodArguments) {
  if (!queryMethodListener) {
    throw new TypeError("reply - not enough arguments");
  }
  postMessage({
    queryMethodListener,
    queryMethodArguments,
  });
}

onmessage = (event) => {
  if (
    event.data instanceof Object &&
    Object.hasOwn(event.data, "queryMethod") &&
    Object.hasOwn(event.data, "queryMethodArguments")
  ) {
    queryableFunctions[event.data.queryMethod].apply(
      self,
      event.data.queryMethodArguments,
    );
  } else {
    defaultReply(event.data);
  }
};

可以切換每個主頁面 -> worker 和 worker -> 主頁面訊息的內容。並且屬性名 "queryMethod"、"queryMethodListeners"、"queryMethodArguments" 可以是任何名稱,只要它們在 QueryableWorkerworker 中保持一致。

透過轉移所有權傳遞資料(可轉移物件)

現代瀏覽器包含一種以高效能方式向 worker 傳遞或從 worker 傳遞某些型別物件的附加方式。可轉移物件 以零複製操作從一個上下文轉移到另一個上下文,這在傳送大型資料集時會帶來巨大的效能提升。

例如,當將 ArrayBuffer 從你的主應用程式轉移到 worker 指令碼時,原始的 ArrayBuffer 將被清除且不再可用。其內容(字面上)被轉移到 worker 上下文。

js
// Create a 32MB "file" and fill it with consecutive values from 0 to 255 – 32MB = 1024 * 1024 * 32
const uInt8Array = new Uint8Array(1024 * 1024 * 32).map((v, i) => i);
worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);

共享資料

SharedArrayBuffer 物件允許兩個執行緒(例如 worker 和主執行緒)同時操作相同的記憶體範圍,並在不透過訊息機制的情況下交換資料。使用共享記憶體確實會帶來重大的確定性、安全性和效能問題,其中一些在 JavaScript 執行模型 一文中概述。

嵌入式 worker

沒有“官方”的方法可以在網頁中嵌入 worker 的程式碼,就像 <script> 元素對普通指令碼所做的那樣。但是,沒有 src 屬性且 type 屬性未標識可執行 MIME 型別的 <script> 元素可以被視為 JavaScript 可以使用的資料塊元素。“資料塊”是 HTML 的一個更通用的功能,可以攜帶幾乎任何文字資料。因此,worker 可以透過這種方式嵌入

html
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <title>MDN Example - Embedded worker</title>
    <script type="text/js-worker">
      // This script WON'T be parsed by JS engines because its MIME type is text/js-worker.
      const myVar = "Hello World!";
      // Rest of your worker code goes here.
    </script>
    <script>
      // This script WILL be parsed by JS engines because its MIME type is text/javascript.
      function pageLog(sMsg) {
        // Use a fragment: browser will only render/reflow once.
        const frag = document.createDocumentFragment();
        frag.appendChild(document.createTextNode(sMsg));
        frag.appendChild(document.createElement("br"));
        document.querySelector("#logDisplay").appendChild(frag);
      }
    </script>
    <script type="text/js-worker">
      // This script WON'T be parsed by JS engines because its MIME type is text/js-worker.
      onmessage = (event) => {
        postMessage(myVar);
      };
      // Rest of your worker code goes here.
    </script>
    <script>
      // This script WILL be parsed by JS engines because its MIME type is text/javascript.

      // In the past blob builder existed, but now we use Blob
      const blob = new Blob(
        Array.prototype.map.call(
          document.querySelectorAll("script[type='text/js-worker']"),
          (script) => script.textContent,
        ),
        { type: "text/javascript" },
      );

      // Creating a new global "worker" variable from all our "text/js-worker" scripts.
      const worker = new Worker(window.URL.createObjectURL(blob));

      worker.onmessage = (event) => {
        pageLog(`Received: ${event.data}`);
      };
    </script>
  </head>
  <body>
    <div id="logDisplay"></div>
    <script>
      // Start the worker.
      worker.postMessage("");
    </script>
  </body>
</html>

嵌入式 worker 現在巢狀在一個新的自定義 document.worker 屬性中。

值得注意的是,你還可以將函式轉換為 Blob,然後從該 Blob 生成物件 URL。例如

js
function fn2workerURL(fn) {
  const blob = new Blob([`(${fn.toString()})()`], { type: "text/javascript" });
  return URL.createObjectURL(blob);
}

更多示例

本節提供瞭如何使用 web worker 的更多示例。

在後臺執行計算

Worker 主要用於允許你的程式碼執行處理器密集型計算,而不會阻塞使用者介面執行緒。在此示例中,worker 用於計算斐波那契數。

JavaScript 程式碼

以下 JavaScript 程式碼儲存在下一節 HTML 中引用的“fibonacci.js”檔案中。

js
self.onmessage = (event) => {
  const userNum = Number(event.data);
  self.postMessage(fibonacci(userNum));
};

function fibonacci(num) {
  let a = 1;
  let b = 0;
  while (num > 0) {
    [a, b] = [a + b, a];
    num--;
  }

  return b;
}

worker 將屬性 onmessage 設定為一個函式,該函式將接收當呼叫 worker 物件的 postMessage() 時傳送的訊息。這會執行數學運算並最終將結果返回到主執行緒。

HTML 程式碼

html
<form>
  <div>
    <label for="number">
      Enter a number that is a zero-based index position in the fibonacci
      sequence to see what number is in that position. For example, enter 6 and
      you'll get a result of 8 — the fibonacci number at index position 6 is 8.
    </label>
    <input type="number" id="number" />
  </div>
  <div>
    <input type="submit" />
  </div>
</form>

<p id="result"></p>

它需要執行以下指令碼,無論是內聯還是作為外部檔案

js
const form = document.querySelector("form");
const input = document.querySelector('input[type="number"]');
const result = document.querySelector("p#result");
const worker = new Worker("fibonacci.js");

worker.onmessage = (event) => {
  result.textContent = event.data;
  console.log(`Got: ${event.data}`);
};

worker.onerror = (error) => {
  console.log(`Worker error: ${error.message}`);
  throw error;
};

form.onsubmit = (e) => {
  e.preventDefault();
  worker.postMessage(input.value);
  input.value = "";
};

網頁建立一個 ID 為 result<p> 元素,用於顯示結果,然後生成 worker。生成 worker 後,onmessage 處理程式被配置為透過設定 <p> 元素的內容來顯示結果,並且 onerror 處理程式被設定為將錯誤訊息記錄到開發工具控制檯。

最後,向 worker 傳送一條訊息以啟動它。

線上嘗試此示例.

在多個 worker 之間分配任務

隨著多核計算機變得越來越普遍,將計算複雜任務分配給多個 worker 通常很有用,這些 worker 可以在多個處理器核心上執行這些任務。

其他型別的 worker

除了專用和共享 web worker,還有其他型別的 worker 可用

  • ServiceWorker 基本上充當代理伺服器,位於 web 應用程式與瀏覽器和網路之間(可用時)。它們旨在(除其他外)實現有效的離線體驗,攔截網路請求並根據網路是否可用和更新的資產是否在伺服器上採取適當的行動。它們還將允許訪問推送通知和後臺同步 API。
  • Audio Worklet 提供了在 worklet(worker 的輕量級版本)上下文中進行直接指令碼化音訊處理的能力。

除錯 worker 執行緒

大多數瀏覽器都允許你在其 JavaScript 偵錯程式中以完全相同的方式除錯 web worker,就像除錯主執行緒一樣!例如,Firefox 和 Chrome 都列出了主執行緒和活動 worker 執行緒的 JavaScript 原始檔,並且所有這些檔案都可以開啟以設定斷點和日誌點。

要了解如何除錯 web worker,請參閱每個瀏覽器的 JavaScript 偵錯程式文件

要為 web worker 開啟開發者工具,你可以使用以下 URL

  • Edge:edge://inspect/
  • Chrome:chrome://inspect/
  • Firefox:about:debugging#/runtime/this-firefox

這些頁面顯示了所有 Service Worker 的概覽。你需要透過 URL 找到相關的 Service Worker,然後點選檢查以訪問該 worker 的控制檯和偵錯程式等開發者工具。

worker 中可用的函式和介面

你可以在 web worker 中使用大多數標準 JavaScript 功能,包括

你在 Worker 中不能做的主要事情是直接影響父頁面。這包括操作 DOM 和使用該頁面的物件。你必須間接地進行,透過 DedicatedWorkerGlobalScope.postMessage() 向主指令碼傳送訊息,然後在事件處理程式中進行更改。

注意: 你可以使用 Worker Playground 測試某個方法或介面是否可用於 worker。

注意: 有關 worker 可用的函式的完整列表,請參閱 worker 可用的函式和介面

規範

規範
HTML
# worker

另見