使用 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

另见