这是重读红宝书(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。
服务工作者线程在两个主要任务上最有用:充当网络请求的缓存层和启用推送通知。在这个意义上,服务工作者线程就是用于把网页变成像原生应用程序一样的工具
服务工作者线程涉及的内容极其广泛,几乎可以单独写一本书