【重读红宝书(JS高程4)】第27章-工作者线程

255 阅读5分钟

这是重读红宝书(JavaScript高级程序设计 第4版)的第4篇文章,本节我们来看看工作者线程:Web Worker专用工作者线程、Shared Worker共享工作者线程、Service Worker 服务工作者线程

本节涉及源码见仓库 github.com/adamswan/Re…

Worker 的最大价值在于:在不改变JS单线程模型的前提下,将主线程的任务交给Worker线程执行

1、是什么

我们每打开一个页面,浏览器会为每个页面都开辟一个独立的运行环境,所以每个页面都有自己的内存、事件循环、DOM等等。这些页面与页面之间是并行运行的,彼此之间是一个个的沙盒关系,不会相互干扰。

Woker,就是让浏览器在页面当前的运行环境下,再分配一个二级子环境。 Worker 线程和 JS 主线程是并行的,所有除了不能操作 DOM 外,我们在 Worker 线程中几乎可以干任何事情,例如:发送网络请求、执行文件输入/输出、进行密集计算、处理大量数据。

2、Worker的分类

1. 专用工作者线程

通常简称为工作者线程、Web Worker 或 Worker,是一种实用的工具,可以让脚本单独创建一个 JS 线程,以执行委托的任务。专用工作者线程,顾名思义,只能被创建它的页面使用。

2. 共享工作者线程

与专用工作者线程非常相似。主要区别是共享工作者线程可以被多个不同的上下文使用,包括不同的页面。任何与创建共享工作者线程的脚本同源的脚本,都可以向共享工作者线程发送消息或从中接收消息。

3. 服务工作者线程

与专用工作者线程和共享工作者线程截然不同。它的主要用途是拦截、重定向和修改页面发出的请求,充当网络请求的仲裁者的角色。

3、专用工作者线程

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      let worker = new Worker("./Worker1.js");

      console.log("打印worker", worker);

      // 1、向 worker 线程发消息
      worker.postMessage("高圆圆");

      // 2、接收 worker 线程的消息
      worker.addEventListener("message", function (data) {
        console.log("来自 worker 线程:", data.data);
      });
    </script>
  </body>
</html>
// 我是一个 worker 线程
// 1、接收 js 主线程的数据
self.addEventListener("message", function (data) {
  console.log("来自js主线程的data:", data.data);
});

// 2、执行耗时任务,将结果发给 js 主线程
setTimeout(() => {
  self.postMessage("刘亦菲1");
}, 1000);

setTimeout(() => {
  self.postMessage("刘亦菲2");
}, 2000);

setTimeout(() => {
  self.postMessage("刘亦菲3");
}, 3000);

new Worker() 做了2件事

1、创建一个 worker 线程(专用工作者线程),它平行于当前页面的 js 主线程

2、返回值是一个对象,即 worker 对象, 它是与专用工作者线程通信的连接点。它可用于在工作者线程和父上下文间传输信息,以及捕获专用工作者线程发出的事件

我们打印这个 worker 对象

实例上的个方法:onerror错误事件、onmessage消息事件

原型上的个方法:postMessage(),用于主线程向 worker 线程发消息;terminate()用于关闭 worker 线程

通信方式:

在 js 主线程中:

通过 worker对象.postMessage()向 worker 线程发消息;

通过监听 worker对象的 message 事件接收来自 worker 线程的消息

在 worker 线程中:

通过 self对象.postMessage()向 js 主线程发消息;

通过监听 self对象的 message 事件接收来自 js 主线程的消息

postMessage()传递消息是最常用的两线程通信方式,并且它是异步的,且采用了结构化克隆算法,因此不能传递像函数这类不可被序列化的数据。此外,两个线程通信还能使用 MessageChannel API、BroadcastChannel API

4、共享工作者线程

共享工作者线程或共享线程与专用工作者线程类似,但可以被多个可信任的执行上下文访问。例如,同源的两个标签页可以访问同一个共享工作者线程。

与专用工作者线程一样,共享工作者线程也在独立执行上下文中运行,也只能与其他上下文异步通信。

通信方式:

通过 MessagePort 在共享工作者线程和父上下文间传递信息,同样也是异步消息

shared-worker.js

// shared-worker.js

// 存储所有连接的端口
const ports = [];

// 监听 connect 事件,当有新的页面连接到这个 SharedWorker 时触发
self.onconnect = function (event) {
  // 获取连接的端口
  const port = event.ports[0];
  // 将新的端口添加到端口数组中
  ports.push(port);

  // 监听端口的消息事件
  port.onmessage = function (event) {
    const message = event.data;
    // 处理接收到的消息,这里简单地将消息广播给所有连接的端口
    ports.forEach((p) => {
      if (p !== port) {
        p.postMessage(`${port}: ${message}`);
      }
    });
  };

  // 启动端口通信
  port.start();
};

页面1

<!DOCTYPE html>
  <html lang="en">
  <head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>页面1</title>
  </head>

  <body>
  <input type="text" id="messageInput" placeholder="Type a message" />
  <button id="sendMessage">向窗口2发送信息</button>
  <!-- 来自窗口2的回应 -->
  <div id="messages"></div>

  <script>
  // 创建 SharedWorker 实例
  const sharedWorker = new SharedWorker("shared-worker.js");
// 获取 worker 的端口
const port = sharedWorker.port;

// 监听端口的消息事件
port.onmessage = function (event) {
  const message = event.data;
  const messageElement = document.createElement("p");
  messageElement.textContent = message;
  document.getElementById("messages").appendChild(messageElement);
};

// 启动端口通信
port.start();

// 给发送按钮添加点击事件监听器
document
  .getElementById("sendMessage")
  .addEventListener("click", function () {
    const input = document.getElementById("messageInput");
    const message = input.value;
    if (message) {
      // 向 worker 发送消息
      port.postMessage(message);
      input.value = "";
    }
  });
</script>
  </body>
  </html>

页面2

<!DOCTYPE html>
  <html lang="en">
  <head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>页面2</title>
  </head>

  <body>
  <input type="text" id="messageInput" placeholder="Type a message" />
  <button id="sendMessage">向窗口1发送信息</button>
  <!-- 来自窗口1的回应  -->
  <div id="messages"></div>

  <script>
  // 创建 SharedWorker 实例
  const sharedWorker = new SharedWorker("shared-worker.js");
// 获取 worker 的端口
const port = sharedWorker.port;

// 监听端口的消息事件
port.onmessage = function (event) {
  const message = event.data;
  const messageElement = document.createElement("p");
  messageElement.textContent = message;
  document.getElementById("messages").appendChild(messageElement);
};

// 启动端口通信
port.start();

// 给发送按钮添加点击事件监听器
document
  .getElementById("sendMessage")
  .addEventListener("click", function () {
    const input = document.getElementById("messageInput");
    const message = input.value;
    if (message) {
      // 向 worker 发送消息
      port.postMessage(message);
      input.value = "";
    }
  });
</script>
  </body>
  </html>

5、服务工作者线程

服务工作者线程(service worker)是一种类似浏览器中代理服务器的线程,可以拦截外出请求和缓 存响应。

这可以让网页在没有网络连接的情况下正常使用,因为部分或全部页面可以从服务工作者线程缓存中提供服务。服务工作者线程也可以使用 Notifications API、Push API、Background Sync API 和 Channel Messaging API。

服务工作者线程在两个主要任务上最有用:充当网络请求的缓存层和启用推送通知。在这个意义上,服务工作者线程就是用于把网页变成像原生应用程序一样的工具

服务工作者线程涉及的内容极其广泛,几乎可以单独写一本书