使用 Web Workers

Web Workers 是一种简单的机制,允许 Web 内容在后台线程中运行脚本。工作线程可以执行任务而不干扰用户界面。此外,它们可以使用 fetch()XMLHttpRequest API 发出网络请求。创建后,工作线程可以通过向由该代码指定的事件处理程序发布消息来向创建它的 JavaScript 代码发送消息(反之亦然)。

本文详细介绍了如何使用 Web Workers。

Web Workers API

Worker 是使用构造函数(例如 Worker())创建的对象,它运行一个命名的 JavaScript 文件——此文件包含将在工作线程中运行的代码;工作线程在另一个与当前 window 不同的全局上下文中运行。因此,在 Worker 中使用 window 快捷方式获取当前全局作用域(而不是 self)将返回错误。

在专用工作线程(由单个脚本使用的标准工作线程;共享工作线程使用 SharedWorkerGlobalScope)的情况下,工作线程上下文由 DedicatedWorkerGlobalScope 对象表示。专用工作线程只能从首先生成它的脚本访问,而共享工作线程可以从多个脚本访问。

注意:有关工作线程的参考文档和更多指南,请参阅 Web Workers API 着陆页

您可以在工作线程中运行任何您喜欢的代码,但有一些例外。例如,您不能直接从工作线程内部操作 DOM,也不能使用 window 对象的一些默认方法和属性。但是,您可以使用 window 下的大量项目,包括 WebSocketsIndexedDB 等数据存储机制。有关更多详细信息,请参阅 Web Workers 可用的函数和类

数据通过消息系统在工作线程和主线程之间发送——双方都使用 postMessage() 方法发送消息,并通过 onmessage 事件处理程序响应消息(消息包含在 message 事件的 data 属性中)。数据被复制而不是共享。

工作线程可以依次生成新的工作线程,只要这些工作线程托管在与父页面相同的 中。

此外,工作线程可以使用 fetch()XMLHttpRequest API 发出网络请求(尽管请注意,XMLHttpRequestresponseXML 属性将始终为 null)。

专用工作线程

如上所述,专用工作线程只能由调用它的脚本访问。在本节中,我们将讨论在我们的 基本专用工作线程示例 (运行专用工作线程) 中找到的 JavaScript:这允许您输入两个要相乘的数字。这些数字被发送到专用工作线程,相乘,结果返回到页面并显示。

此示例相当简单,但我们决定在向您介绍基本工作线程概念时保持其简单性。文章后面将介绍更高级的细节。

Worker 特性检测

为了进行更受控的错误处理和向后兼容性,最好将您的工作线程访问代码包装在以下代码中 (main.js)

js
if (window.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 可能指向打包器无法控制的文件,因此它无法做出任何假设)。

向专用工作线程发送和接收消息

工作线程的魔力通过 postMessage() 方法和 onmessage 事件处理程序实现。当您想要向工作线程发送消息时,您可以像这样向其发布消息 (main.js)

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

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

因此,这里我们有两个由变量 firstsecond 表示的 <input> 元素;当其中任一元素的值发生更改时,myWorker.postMessage([first.value,second.value]) 用于将两个元素内部的值作为数组发送到工作线程。您可以在消息中发送几乎任何您想要的内容。

在工作线程中,当接收到消息时,我们可以通过编写如下事件处理程序块来响应 (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 来响应从工作线程发送回来的消息

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

在这里,我们获取消息事件数据并将其设置为结果段落的 textContent,以便用户可以看到计算结果。

注意:请注意,在主脚本线程中使用时,onmessagepostMessage() 需要挂在 Worker 对象上,但在工作线程中使用时则不需要。这是因为,在工作线程内部,工作线程实际上是全局作用域。

注意:当消息在主线程和工作线程之间传递时,它会被复制或“传输”(移动),而不是共享。阅读 向工作线程传输数据和从工作线程接收数据:更多细节 以获取更详细的解释。

终止工作线程

如果您需要立即从主线程终止正在运行的工作线程,您可以通过调用工作线程的 terminate 方法来实现

js
myWorker.terminate();

工作线程立即被终止。

处理错误

当工作线程中发生运行时错误时,将调用其 onerror 事件处理程序。它接收一个名为 error 的事件,该事件实现了 ErrorEvent 接口。

该事件不会冒泡并且是可取消的;为了防止默认操作发生,工作线程可以调用错误事件的 preventDefault() 方法。

错误事件有以下三个感兴趣的字段

message

人类可读的错误消息。

filename

发生错误的脚本文件的名称。

lineno

发生错误的脚本文件的行号。

生成子工作线程

如果需要,工作线程可以生成更多工作线程。所谓的子工作线程必须托管在与父页面相同的源中。此外,子工作线程的 URI 相对于父工作线程的位置解析,而不是相对于拥有页面的位置解析。这使工作线程更容易跟踪其依赖项在哪里。

导入脚本和库

工作线程可以访问一个全局函数 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 */

浏览器加载每个列出的脚本并执行它。然后,工作线程可以使用每个脚本中的任何全局对象。如果脚本无法加载,则会抛出 NETWORK_ERROR,并且后续代码将不会执行。但是,先前执行的代码(包括使用 setTimeout() 延迟的代码)仍然可以正常工作。importScripts() 方法**之后**的函数声明也会保留,因为这些函数总是在其余代码之前进行评估。

注意:脚本可以按任何顺序下载,但将按照您将文件名传递到 importScripts() 中的顺序执行。这是同步完成的;importScripts() 只有在所有脚本都加载并执行后才会返回。

共享工作线程

共享工作线程可供多个脚本访问——即使它们被不同的窗口、iframe 甚至其他工作线程访问。在本节中,我们将讨论我们基本共享工作线程示例运行共享工作线程)中的 JavaScript 代码:这与基本专用工作线程示例非常相似,除了它提供了两个由不同脚本文件处理的函数:将两个数字相乘对一个数字进行平方。这两个脚本都使用同一个工作线程来执行所需的实际计算。

在这里,我们将重点关注专用工作线程和共享工作线程之间的区别。请注意,在本示例中,我们有两个 HTML 页面,每个页面都应用了使用同一个工作线程文件的 JavaScript 代码。

注意:如果多个浏览器上下文可以访问 SharedWorker,则所有这些浏览器上下文必须共享完全相同的源(相同的协议、主机和端口)。

注意:在 Firefox 中,私有窗口和非私有窗口中加载的文档之间无法共享共享工作线程(Firefox 错误 1177621)。

生成共享工作线程

生成新的共享工作线程与生成专用工作线程非常相似,但构造函数名称不同(请参阅index.htmlindex2.html)——每个页面都必须使用以下代码启动工作线程

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

一个很大的区别是,使用共享工作线程时,必须通过port对象进行通信——会打开一个显式端口,脚本可以使用该端口与工作线程通信(在专用工作线程的情况下,这是隐式完成的)。

端口连接需要通过onmessage事件处理程序隐式启动,或者在发送任何消息之前通过start()方法显式启动。只有在通过addEventListener()方法连接message事件时,才需要调用start()

注意:如果需要双向通信,则使用start()方法打开端口连接时,需要在父线程和工作线程中都调用它。

向共享工作线程发送和接收消息

现在可以像以前一样向工作线程发送消息,但必须通过端口对象调用postMessage()方法(同样,您将在multiply.jssquare.js 中看到类似的构造)。

js
squareNumber.onchange = () => {
  myWorker.port.postMessage([squareNumber.value, squareNumber.value]);
  console.log("Message posted to 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处理程序来执行计算并将结果返回到主线程。在工作线程中设置此onmessage处理程序也会隐式打开返回父线程的端口连接,因此实际上不需要调用port.start(),如上所述。

最后,回到主脚本中,我们处理消息(同样,您将在multiply.jssquare.js 中看到类似的构造)。

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

当来自工作线程的消息通过端口返回时,我们将计算结果插入相应的段落中。

关于线程安全

Worker 接口生成真正的操作系统级线程,细心的程序员可能会担心,如果不小心,并发可能会导致代码出现“有趣”的影响。

但是,由于 Web 工作线程与其他线程的通信点受到严格控制,因此实际上很难导致并发问题。无法访问非线程安全组件或 DOM。并且您必须通过序列化对象将特定数据传入和传出线程。因此,您必须非常努力才能在代码中导致问题。

内容安全策略

工作线程被认为拥有自己的执行上下文,独立于创建它们的文档。因此,它们通常不受创建它们的文件(或父工作线程)的内容安全策略的约束。例如,假设一个文档带有以下标头

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

除其他事项外,这将阻止它包含的任何脚本使用eval()。但是,如果脚本构造了一个工作线程,则在工作线程上下文中运行的代码被允许使用eval()

要为工作线程指定内容安全策略,请为传递工作线程脚本本身的请求设置Content-Security-Policy响应标头。

唯一的例外情况是,如果工作线程的源是全局唯一标识符(例如,如果其 URL 的方案为 data 或 blob)。在这种情况下,工作线程确实会继承创建它的文档或工作线程的 CSP。

向工作线程传输数据和从工作线程接收数据:更多细节

在主页面和工作线程之间传递的数据会被复制,而不是共享。对象在传递给工作线程时会被序列化,然后在另一端反序列化。页面和工作线程不共享同一个实例,因此最终结果是在每一端都会创建副本。大多数浏览器将此功能实现为结构化克隆

为了说明这一点,让我们创建一个名为emulateMessage()的函数,它将模拟在从worker到主页面或反之亦然的过程中克隆而不是共享的值的行为。

js
function emulateMessage(vVal) {
  return eval(`(${JSON.stringify(vVal)})`);
}

// Tests

// test #1
const example1 = new Number(3);
console.log(typeof example1); // object
console.log(typeof emulateMessage(example1)); // number

// test #2
const example2 = true;
console.log(typeof example2); // boolean
console.log(typeof emulateMessage(example2)); // boolean

// test #3
const example3 = new String("Hello World");
console.log(typeof example3); // object
console.log(typeof emulateMessage(example3)); // string

// test #4
const example4 = {
  name: "Carina Anand",
  age: 43,
};
console.log(typeof example4); // object
console.log(typeof emulateMessage(example4)); // object

// test #5
function Animal(type, age) {
  this.type = type;
  this.age = age;
}
const example5 = new Animal("Cat", 3);
alert(example5.constructor); // Animal
alert(emulateMessage(example5).constructor); // Object

克隆而不是共享的值称为消息。您可能已经知道,可以通过使用postMessage()向主线程发送和接收消息,并且message事件的data属性包含从工作线程传递回来的数据。

example.html:(主页面)

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

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

myWorker.postMessage("ali");

my_task.js(工作线程)

js
postMessage("I'm working before postMessage('ali').");

onmessage = (event) => {
  postMessage(`Hi, ${event.data}`);
};

结构化克隆算法可以接受 JSON 和 JSON 不能接受的一些内容——例如循环引用。

传递数据示例

示例 1:高级传递 JSON 数据和创建切换系统

如果必须传递一些复杂数据,并且必须在主页面和工作线程中调用许多不同的函数,则可以创建一个将所有内容组合在一起的系统。

首先,我们创建一个QueryableWorker类,它接收工作线程的 URL、默认侦听器和错误处理程序,并且此类将跟踪侦听器列表并帮助我们与工作线程通信。

js
function QueryableWorker(url, defaultListener, onError) {
  const instance = this;
  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];
};

在这里,我们让工作线程处理两个简单的操作以进行说明:获取两个数字的差并在三秒后发出警报。为了实现这一点,我们首先实现一个sendQuery方法,该方法查询工作线程是否实际上具有执行我们想要操作的相应方法。

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。如果工作线程具有我们查询的相应方法,则它应该返回相应侦听器的名称及其所需的参数,我们只需要在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(
      instance,
      event.data.queryMethodArguments,
    );
  } else {
    this.defaultListener.call(instance, event.data);
  }
};

现在转到工作线程。首先,我们需要拥有处理这两个简单操作的方法。

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
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <title>MDN Example - Queryable worker</title>
    <script type="text/javascript">
      // 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 instance = this;
        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(
              instance,
              event.data.queryMethodArguments,
            );
          } else {
            this.defaultListener.call(instance, 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} :-)`);
      });
    </script>
  </head>
  <body>
    <ul>
      <li>
        <a
          id="firstLink"
          href="javascript:myTask.sendQuery('getDifference', 5, 3);"
          >What is the difference between 5 and 3?</a
        >
      </li>
      <li>
        <a href="javascript:myTask.sendQuery('waitSomeTime');"
          >Wait 3 seconds</a
        >
      </li>
      <li>
        <a href="javascript:myTask.terminate();">terminate() the Worker</a>
      </li>
    </ul>
  </body>
</html>

my_task.js(工作线程)

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);
  }
};

可以切换每个主页面->工作线程和工作线程->主页面消息的内容。并且属性名称“queryMethod”、“queryMethodListeners”、“queryMethodArguments”可以是任何名称,只要它们在QueryableWorkerworker中一致即可。

通过转移所有权传递数据(可转移对象)

现代浏览器提供了一种额外的通过高性能将某些类型的对象传递到工作线程或从工作线程传递回来的方法。可转移对象通过零拷贝操作从一个上下文转移到另一个上下文,这在发送大型数据集时会带来巨大的性能提升。

例如,当将ArrayBuffer从主应用程序传输到工作线程脚本时,原始的ArrayBuffer将被清空并且不再可用。其内容(从字面上讲)被传输到工作线程上下文。

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]);

嵌入式工作线程

没有“官方”方法可以将工作线程的代码嵌入到网页中,就像<script>元素对普通脚本所做的那样。但是,没有src属性并且type属性未识别可执行 MIME 类型的<script>元素可以被视为数据块元素,JavaScript 可以使用它。“数据块”是 HTML 的一个更通用的特性,可以承载几乎任何文本数据。因此,工作线程可以以这种方式嵌入。

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 document.worker property containing all our "text/js-worker" scripts.
      document.worker = new Worker(window.URL.createObjectURL(blob));

      document.worker.onmessage = (event) => {
        pageLog(`Received: ${event.data}`);
      };

      // Start the worker.
      window.onload = () => {
        document.worker.postMessage("");
      };
    </script>
  </head>
  <body>
    <div id="logDisplay"></div>
  </body>
</html>

嵌入的工作线程现在嵌套到新的自定义document.worker属性中。

还值得注意的是,您还可以将函数转换为 Blob,然后从该 Blob 生成对象 URL。例如

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

更多示例

本节提供了有关如何使用 Web 工作线程的更多示例。

在后台执行计算

工作线程主要用于允许您的代码执行处理器密集型计算,而不会阻塞用户界面线程。在本示例中,工作线程用于计算斐波那契数列。

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;
}

工作线程将属性onmessage设置为一个函数,该函数将在调用工作线程对象的postMessage()时接收发送的消息。这将执行数学运算并最终将结果返回到主线程。

HTML 代码

html
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="UTF-8" />
    <title>Fibonacci number generator</title>
    <style>
      body {
        width: 500px;
      }

      div,
      p {
        margin-bottom: 20px;
      }
    </style>
  </head>
  <body>
    <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>

    <script>
      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 = "";
      };
    </script>
  </body>
</html>

网页创建一个 ID 为result<p>元素,该元素用于显示结果,然后生成工作线程。生成工作线程后,onmessage处理程序配置为通过设置<p>元素的内容来显示结果,并且onerror处理程序设置为将错误消息记录到开发者工具控制台中。

最后,向工作线程发送一条消息以启动它。

尝试此示例.

在多个工作线程之间划分任务

随着多核计算机变得越来越普遍,通常需要在多个工作线程之间划分计算复杂的任务,然后这些工作线程可以在多个处理器内核上执行这些任务。

其他类型的 worker

除了专用和共享 Web 工作线程之外,还有其他类型的可用工作线程。

  • ServiceWorkers本质上充当代理服务器,位于 Web 应用程序、浏览器和网络(如果可用)之间。它们旨在(除其他事项外)实现有效的脱机体验,拦截网络请求并根据网络是否可用以及服务器上是否存在更新的资产采取相应的措施。它们还将允许访问推送通知和后台同步 API。
  • Audio Worklet 提供了在 worklet(工作线程的轻量级版本)上下文中直接执行脚本音频处理的能力。

调试工作线程

大多数浏览器允许您以完全相同的方式在它们的 JavaScript 调试器中调试 Web 工作线程,就像调试主线程一样!例如,Firefox 和 Chrome 都列出了主线程和活动工作线程的 JavaScript 源文件,并且可以打开所有这些文件以设置断点和日志点。

要了解如何调试 Web Workers,请参阅每个浏览器 JavaScript 调试器的文档。

工作线程中可用的函数和接口

您可以在 Web Worker 内部使用大多数标准 JavaScript 功能,包括

您在 Worker 中无法执行的主要操作是直接影响父页面。这包括操作 DOM 和使用该页面的对象。您必须间接地执行此操作,方法是通过 DedicatedWorkerGlobalScope.postMessage 向主脚本发送消息,然后在事件处理程序中进行更改。

注意:您可以使用以下网站测试某个方法是否可用于 Worker:https://worker-playground.glitch.me/。例如,如果您在 Firefox 84 上的网站中输入 EventSource,您会看到它在 Service Worker 中不受支持,但在专用和共享 Worker 中受支持。

注意:有关 Worker 可用函数的完整列表,请参阅 Worker 可用的函数和接口

规范

规范
HTML 标准
# Worker

另请参阅