前言
前面的文章已经介绍了postMessage、localStorage、messageChannel、broadcastChannel以及window.name。今天要介绍一种"多页面协同"场景的工具------SharedWorker。
不同于普通Worker只能被单个页面独占,SharedWorker能被同一域名下的多个页面共享 ,实现高效的"多页面数据中枢"。本文就带你了解SharedWorker跨页面通讯的核心用法。
1. 什么是SharedWorker?
在介绍SharedWorker之前,我们先回顾下Worker的基本概念:Worker是HTML5引入的后台线程机制,能让JavaScript在主线程之外运行,避免复杂计算阻塞页面渲染。而SharedWorker,顾名思义,是"可共享的Worker",它有两个核心特点:
- 跨页面共享:同一域名下的多个标签页、iframe,甚至不同窗口,都能连接到同一个SharedWorker实例,实现数据互通。
- 独立线程:运行在独立于所有页面主线程的后台线程中,既不会阻塞页面,也能统一处理多页面的请求。
- 域名隔离:遵循同源策略,只有相同协议、域名、端口的页面,才能共享同一个SharedWorker。
简单来说,SharedWorker就像一个"公共服务端",多个页面作为"客户端"与之建立连接,通过它完成数据的传递与协同。和普通Worker的"一对一"模式不同,它是"一对多"的通讯方案。
2. SharedWorker如何实现跨页面通讯?
SharedWorker的通讯原理可以概括为"单一实例+多端口连接",先看一张图:

具体流程是:
- 创建实例 :每个页面通过
new SharedWorker('./share.js')创建实例时,浏览器会启动一个SharedWorker后台线程,加载指定的脚本文件,这个脚本文件是共享。 - 端口建立:每个页面连接到SharedWorker后,都会与Worker建立一个独立的"消息端口"(MessagePort),这是页面与Worker之间的通讯通道。
- 数据传递 :页面和Worker都是通过通过端口发送消息
port.postMessage,都是通过port.onmessage接收数据进行处理,再通过端口将结果反馈给单个页面,或广播给所有连接的页面。 - 实例销毁:当所有连接到SharedWorker的页面都关闭时,Worker后台线程才会被浏览器销毁,释放资源。
说了这么多,接下来我们进行实践操作。
3. 实践案例
SharedWorker的使用分为"Worker脚本"和"页面脚本"两部分,Worker脚本负责核心逻辑,页面脚本负责建立连接和收发消息。
实现需求:同一域名下的pageA和pageB页面,通过SharedWorker实现消息互发,且任意页面可以通过Worker广播消息给所有页面。
可以先看一张页面如何连接到Worker流程图:

页面端我们只需要接收发送消息即可,Worker端我们需要收集注册的端口并进行逻辑处理,下面我们一步步来实现:
3.1 步骤1:编写SharedWorker核心脚本(share.js)
share.js的核心逻辑:是使用Map结构,通过connect将所有连接Worker的页面进行收集端口,每个页面需要唯一的pageId用于后续私发消息。
页面发送数据分为三种,进入页面需要注册到share.js,发送广播消息,发送私信需要target页面的pageId。 数据如下:
| 类型 | 用途 | 参数 |
|---|---|---|
register |
页面注册 | pageId |
broadcast |
广播消息 | pageId, data |
private |
点对点消息 | pageId, target, data |
js
// 注册页面
port.postMessage({
type: 'register',
pageId: 'page-123'
});
// 发送广播
port.postMessage({
type: 'broadcast',
pageId: 'page-123',
data: 'Hello everyone!'
});
// 发送私信
port.postMessage({
type: 'private',
pageId: 'page-123',
target: 'page-456',
data: 'Hi page-456!'
});
share.js脚本:
js
// 存储所有页面与Worker的连接端口(使用 Map,key 是 pageId,value 是 port)
const connections = new Map();
console.log('打印***self', self)
// 监听新页面的连接请求
self.addEventListener('connect', (e) => {
// 获取当前页面的通讯端口
const port = e.ports[0];
// 注意:此时还不知道 pageId,需要等待 register 消息
console.log(`新页面尝试连接,等待注册...`);
// 允许端口通讯
port.start();
// 向页面发送连接成功消息(用于调试)
port.postMessage({
from: 'Worker',
type: 'connected',
data: `Worker 已连接,当前连接数:${connections.size}`
});
// 监听端口的消息事件(接收页面发来的消息)
port.onmessage = (msg) => {
console.log('Worker收到消息:', msg.data);
const { type, target, data, pageId } = msg.data;
// 0. 处理注册消息(页面连接时立即发送)
if (type === 'register') {
// 如果该 pageId 已存在,说明是页面刷新,先关闭旧连接
if (connections.has(pageId)) {
const oldPort = connections.get(pageId);
console.log(`页面 ${pageId} 刷新,清理旧连接`);
try {
oldPort.close();
} catch (e) {
// 忽略关闭错误
}
}
// 保存新连接
port.pageId = pageId;
connections.set(pageId, port);
console.log(`页面 ${pageId} 已注册,当前在线:[${Array.from(connections.keys()).join(', ')}]`,connections);
return;
}
// 保存页面标识到端口对象(兼容旧的发送方式)
if (pageId && !port.pageId) {
port.pageId = pageId;
connections.set(pageId, port);
console.log(`页面 ${pageId} 已注册(兼容模式)`);
}
// 1. 广播消息:发送给所有连接的页面
if (type === 'broadcast') {
console.log(`广播消息给 ${connections.size} 个连接`);
connections.forEach((conn, id) => {
try {
conn.postMessage({ from: 'Worker', data: `广播消息:${data}` });
} catch (e) {
console.log(`发送给 ${id} 失败,移除连接`);
connections.delete(id);
}
});
}
// 2. 点对点消息:仅发送给目标页面(这里用页面标识区分)
else if (type === 'private') {
console.log(`私发消息给 ${target},当前连接:[${Array.from(connections.keys()).join(', ')}]`);
if (connections.has(target)) {
try {
connections.get(target).postMessage({ from: 'Worker', data: `私发消息:${data}` });
} catch (e) {
console.log(`发送给 ${target} 失败,移除连接`);
connections.delete(target);
}
} else {
console.log(`警告:未找到目标页面 ${target}`);
}
}
};
// 监听端口关闭事件(页面关闭或主动断开)
port.addEventListener('close', () => {
// 通过 pageId 删除连接
if (port.pageId) {
connections.delete(port.pageId);
console.log(`页面 ${port.pageId} 断开连接,当前在线:[${Array.from(connections.keys()).join(', ')}]`);
}
});
// 允许端口通讯(必须调用,否则无法收发消息)
port.start();
});
3.2 步骤二:编写页面脚本(pageA.html 和 pageB.html)
代码逻辑如下:唯一需要区别的是pageId,页面B只需将pageId改为page-456,并调整"发给页面B"按钮的逻辑为"发给页面A"即可。
页面的创建流程如下:
js
1. 页面创建 SharedWorker 实例
↓
2. 调用 port.start() 启动通信
↓
3. 注册 onmessage 监听器
↓
4. 发送 register 消息(携带 pageId)
↓
5. Worker 保存连接到 Map: connections.set(pageId, port)
↓
6. 页面可以开始发送/接收消息
代码如下:
js
<body>
<h3>页面A(标识:page-123)</h3>
<input type="text" id="msgInput" placeholder="输入消息">
<button onclick="sendBroadcast()">广播消息</button>
<button onclick="sendToPageB()">发给页面B</button>
<div id="log"></div>
<script>
// 页面唯一标识
const pageId = 'page-123';
// 1. 创建SharedWorker实例,指定Worker脚本路径
const worker = new SharedWorker('./share.js');
// 2. 获取与Worker的通讯端口
const port = worker.port;
// 3. 允许端口通讯(必须在监听器之前调用)
port.start();
// 4. 监听Worker发来的消息(使用onmessage更可靠)
port.onmessage = (msg) => {
console.log('页面A收到消息:', msg.data);
const log = document.getElementById('log');
log.innerHTML += `<p>收到:${JSON.stringify(msg.data)}</p>`;
};
// 5. 连接成功后立即发送注册消息
port.postMessage({
type: 'register',
pageId: pageId
});
// 发送广播消息
function sendBroadcast() {
const input = document.getElementById('msgInput');
port.postMessage({
type: 'broadcast',
pageId: pageId,
data: input.value
});
}
// 发送点对点消息给页面B(页面B标识为page-456)
function sendToPageB() {
const input = document.getElementById('msgInput');
port.postMessage({
type: 'private',
pageId: pageId,
target: 'page-456',
data: input.value
});
}
// 页面关闭时断开连接
window.addEventListener('beforeunload', () => {
port.close();
});
</script>
</body>
3.3 实际调试
3.3.1 如何调试share.js
通过Chrome如何查看share.js中打印的数据,页面是无法访问的,因为它是独立的控制台。
为什么控制台要独立?这是因为SharedWorker运行在与页面主线程完全隔离的独立线程中,从浏览器架构和安全设计出发,其控制台输出也需与页面线程分离,避免线程间的信息干扰。
打开步骤:
- 在Chrome浏览器中直接访问地址:
chrome://inspect/#workers; - 页面会列出当前浏览器中所有运行的Worker实例,找到目标SharedWorker对应的"inspect"链接并点击,即可打开专属控制台。

控制台打印:

3.3.2 页面发送广播或私发消息
- 将share.js、pageA.html、pageB.html部署到同一域名下(本地可用live-server启动)。
- 同时打开两个页面,在页面A输入消息并点击"广播消息",两个页面都会收到Worker转发的消息。
- 点击"发给页面B",只有页面B会收到消息,实现点对点通讯。

页面关闭,自动销毁

4、SharedWorker的注意事项
4.1 同源策略限制严格
SharedWorker的同源限制比普通Worker更严格:协议、域名、端口必须完全一致,即使是子域名(如a.example.com和b.example.com)也无法共享。
4.2 必须部署才能运行,本地直接打开无效
由于浏览器的安全限制,直接双击本地HTML文件(file://协议)创建SharedWorker会报错。必须通过HTTP/HTTPS协议部署,本地可使用live-server、http-server等工具启动服务。
4.3 端口通讯需"双向启动"
页面端和Worker端的port.start()必须都调用,否则无法正常收发消息。尤其是在使用addEventListener绑定消息事件时,这一步不能省略(若用onmessage属性绑定,部分浏览器会自动启动,但建议统一调用start())。
4.4 消息数据需可序列化
通过port传递的消息数据,必须是可结构化克隆的类型(如对象、数组、字符串等),不能传递函数、DOM元素等不可序列化的数据。若需传递复杂数据,可先转为JSON字符串,接收后再解析。
5. 总结
最后总结一下:SharedWorker是一个能被同源多页面共享的后台线程,它通过"单一实例+管理多端口"的模式,实现跨页面通信与数据协同。