前端跨页面通讯终极指南⑥:SharedWorker 用法全解析

作者:一诺滚雪球日期:2025/12/14

前言

前面的文章已经介绍了postMessagelocalStoragemessageChannelbroadcastChannel以及window.name。今天要介绍一种“多页面协同”场景的工具——SharedWorker

不同于普通Worker只能被单个页面独占,SharedWorker能被同一域名下的多个页面共享,实现高效的“多页面数据中枢”。本文就带你了解SharedWorker跨页面通讯的核心用法。

1. 什么是SharedWorker?

在介绍SharedWorker之前,我们先回顾下Worker的基本概念:Worker是HTML5引入的后台线程机制,能让JavaScript在主线程之外运行,避免复杂计算阻塞页面渲染。而SharedWorker,顾名思义,是“可共享的Worker”,它有两个核心特点:

  • 跨页面共享:同一域名下的多个标签页、iframe,甚至不同窗口,都能连接到同一个SharedWorker实例,实现数据互通。
  • 独立线程:运行在独立于所有页面主线程的后台线程中,既不会阻塞页面,也能统一处理多页面的请求。
  • 域名隔离:遵循同源策略,只有相同协议、域名、端口的页面,才能共享同一个SharedWorker。

简单来说,SharedWorker就像一个“公共服务端”,多个页面作为“客户端”与之建立连接,通过它完成数据的传递与协同。和普通Worker的“一对一”模式不同,它是“一对多”的通讯方案。

2. SharedWorker如何实现跨页面通讯?

SharedWorker的通讯原理可以概括为“单一实例+多端口连接”,先看一张图:

image.png

具体流程是:

  1. 创建实例:每个页面通过new SharedWorker('./share.js')创建实例时,浏览器会启动一个SharedWorker后台线程,加载指定的脚本文件,这个脚本文件是共享。
  2. 端口建立:每个页面连接到SharedWorker后,都会与Worker建立一个独立的“消息端口”(MessagePort),这是页面与Worker之间的通讯通道。
  3. 数据传递:页面和Worker都是通过通过端口发送消息port.postMessage,都是通过port.onmessage接收数据进行处理,再通过端口将结果反馈给单个页面,或广播给所有连接的页面。
  4. 实例销毁:当所有连接到SharedWorker的页面都关闭时,Worker后台线程才会被浏览器销毁,释放资源。

说了这么多,接下来我们进行实践操作。

3. 实践案例

SharedWorker的使用分为“Worker脚本”和“页面脚本”两部分,Worker脚本负责核心逻辑,页面脚本负责建立连接和收发消息。

实现需求:同一域名下的pageA和pageB页面,通过SharedWorker实现消息互发,且任意页面可以通过Worker广播消息给所有页面。

可以先看一张页面如何连接到Worker流程图:

image.png

页面端我们只需要接收发送消息即可,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
1// 注册页面
2port.postMessage({                                              
3    type: 'register',                                           
4    pageId: 'page-123'                                          
5});                                                            
6// 发送广播                                                                     
7port.postMessage({                               
8   type: 'broadcast',                                          
9   pageId: 'page-123',                                         
10   data: 'Hello everyone!'                                     
11});                                                             
12// 发送私信                                                                     
13port.postMessage({                               
14   type: 'private',                                            
15   pageId: 'page-123',                                         
16   target: 'page-456',                                         
17   data: 'Hi page-456!'                                        
18}); 
19

share.js脚本:

1// 存储所有页面与Worker的连接端口(使用 Map,key  pageId,value  port)
2const connections = new Map();
3
4console.log('打印***self', self)
5// 监听新页面的连接请求
6self.addEventListener('connect', (e) => {
7    // 获取当前页面的通讯端口
8    const port = e.ports[0];
9
10    // 注意:此时还不知道 pageId,需要等待 register 消息
11    console.log(`新页面尝试连接,等待注册...`);
12
13    // 允许端口通讯
14    port.start();
15
16    // 向页面发送连接成功消息(用于调试)
17    port.postMessage({
18        from: 'Worker',
19        type: 'connected',
20        data: `Worker 已连接,当前连接数:${connections.size}`
21    });
22
23    // 监听端口的消息事件(接收页面发来的消息)
24    port.onmessage = (msg) => {
25        console.log('Worker收到消息:', msg.data);
26        const { type, target, data, pageId } = msg.data;
27
28        // 0. 处理注册消息(页面连接时立即发送)
29        if (type === 'register') {
30            // 如果该 pageId 已存在,说明是页面刷新,先关闭旧连接
31            if (connections.has(pageId)) {
32                const oldPort = connections.get(pageId);
33                console.log(`页面 ${pageId} 刷新,清理旧连接`);
34                try {
35                    oldPort.close();
36                } catch (e) {
37                    // 忽略关闭错误
38                }
39            }
40
41            // 保存新连接
42            port.pageId = pageId;
43            connections.set(pageId, port);
44            console.log([`页面 ${pageId} 已注册,当前在线:[${Array.from(connections.keys()).join(', ')}]`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.join.md),connections);
45            return;
46        }
47
48        // 保存页面标识到端口对象(兼容旧的发送方式)
49        if (pageId && !port.pageId) {
50            port.pageId = pageId;
51            connections.set(pageId, port);
52            console.log(`页面 ${pageId} 已注册(兼容模式)`);
53        }
54
55        // 1. 广播消息:发送给所有连接的页面
56        if (type === 'broadcast') {
57            console.log(`广播消息给 ${connections.size} 个连接`);
58            connections.forEach((conn, id) => {
59                try {
60                    conn.postMessage({ from: 'Worker', data: [`广播消息:${data}`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.data.md) });
61                } catch (e) {
62                    console.log([`发送给 ${id} 失败,移除连接`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.id.md));
63                    connections.delete(id);
64                }
65            });
66        }
67        // 2. 点对点消息:仅发送给目标页面(这里用页面标识区分)
68        else if (type === 'private') {
69            console.log([`私发消息给 ${target},当前连接:[${Array.from(connections.keys()).join(', ')}]`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.join.md));
70
71            if (connections.has(target)) {
72                try {
73                    connections.get(target).postMessage({ from: 'Worker', data: [`私发消息:${data}`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.data.md) });
74                } catch (e) {
75                    console.log(`发送给 ${target} 失败,移除连接`);
76                    connections.delete(target);
77                }
78            } else {
79                console.log(`警告:未找到目标页面 ${target}`);
80            }
81        }
82    };
83
84    // 监听端口关闭事件(页面关闭或主动断开)
85    port.addEventListener('close', () => {
86        // 通过 pageId 删除连接
87        if (port.pageId) {
88            connections.delete(port.pageId);
89            console.log([`页面 ${port.pageId} 断开连接,当前在线:[${Array.from(connections.keys()).join(', ')}]`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.join.md));
90        }
91    });
92
93    // 允许端口通讯(必须调用,否则无法收发消息)
94    port.start();
95});
96
97

3.2 步骤二:编写页面脚本(pageA.html 和 pageB.html)

代码逻辑如下:唯一需要区别的是pageId,页面B只需将pageId改为page-456,并调整“发给页面B”按钮的逻辑为“发给页面A”即可。

页面的创建流程如下:

11. 页面创建 SharedWorker 实例
2   
32. 调用 port.start() 启动通信
4   
53. 注册 onmessage 监听器
6   
74. 发送 register 消息(携带 pageId)
8   
95. Worker 保存连接到 Map: connections.set(pageId, port)
10   
116. 页面可以开始发送/接收消息
12

代码如下:

1<body>
2    <h3>页面A(标识:page-123</h3>
3    <input type="text" id="msgInput" placeholder="输入消息">
4    <button onclick="sendBroadcast()">广播消息</button>
5    <button onclick="sendToPageB()">发给页面B</button>
6    <div id="log"></div>
7
8    <script>
9        // 页面唯一标识
10        const pageId = 'page-123';
11        // 1. 创建SharedWorker实例,指定Worker脚本路径
12        const worker = new SharedWorker('./share.js');
13        // 2. 获取与Worker的通讯端口
14        const port = worker.port;
15
16        // 3. 允许端口通讯(必须在监听器之前调用)
17        port.start();
18
19        // 4. 监听Worker发来的消息(使用onmessage更可靠)
20        port.onmessage = (msg) => {
21            console.log('页面A收到消息:', msg.data);
22            const log = document.getElementById('log');
23            log.innerHTML += [`<p>收到:${JSON.stringify(msg.data)}</p>`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.p.md);
24        };
25
26        // 5. 连接成功后立即发送注册消息
27        port.postMessage({
28            type: 'register',
29            pageId: pageId
30        });
31
32        // 发送广播消息
33        function sendBroadcast() {
34            const input = document.getElementById('msgInput');
35            port.postMessage({
36                type: 'broadcast',
37                pageId: pageId,
38                data: input.value
39            });
40        }
41
42        // 发送点对点消息给页面B(页面B标识为page-456)
43        function sendToPageB() {
44            const input = document.getElementById('msgInput');
45            port.postMessage({
46                type: 'private',
47                pageId: pageId,
48                target: 'page-456',
49                data: input.value
50            });
51        }
52
53        // 页面关闭时断开连接
54        window.addEventListener('beforeunload', () => {
55            port.close();
56        });
57    </script>
58</body>
59

3.3 实际调试

3.3.1 如何调试share.js

通过Chrome如何查看share.js中打印的数据,页面是无法访问的,因为它是独立的控制台。

为什么控制台要独立?这是因为SharedWorker运行在与页面主线程完全隔离的独立线程中,从浏览器架构和安全设计出发,其控制台输出也需与页面线程分离,避免线程间的信息干扰。

打开步骤:

  1. 在Chrome浏览器中直接访问地址:chrome://inspect/#workers
  2. 页面会列出当前浏览器中所有运行的Worker实例,找到目标SharedWorker对应的“inspect”链接并点击,即可打开专属控制台。

image.png

控制台打印:

image.png

3.3.2 页面发送广播或私发消息

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

image.png

页面关闭,自动销毁

image.png

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是一个能被同源多页面共享的后台线程,它通过“单一实例+管理多端口”的模式,实现跨页面通信与数据协同。


前端跨页面通讯终极指南⑥:SharedWorker 用法全解析》 是转载文章,点击查看原文


相关推荐


智能家政系统架构设计与核心模块解析
小码哥0682025/12/5

一、开发背景           上班族家庭:由于工作繁忙,无暇顾及家务,对日常保洁、家电清洗等便捷高效的家政服务需求强烈,希望能够通过简单的操作,在合适的时间预约到专业的家政人员上门服务,并且对服务质量和服务人员的专业性有较高要求         一些企业为员工提供福利,会定期采购家政服务,如办公室清洁、企业食堂后勤服务等;同时,医疗机构、学校、酒店等也需要专业的家政服务来保障环境清洁和卫生维护,对服务的标准化、规模化有较高要求。 二、家政服务平台技术分析 前端展示层      


你以为 Props 只是传参? 不,它是 React 组件设计的“灵魂系统”
白兰地空瓶2025/12/22

90% 的 React 初学者,都低估了 Props。 他们以为它只是“从父组件往子组件传点数据”。 但真正写过复杂组件、设计过通用组件的人都知道一句话: Props 决定了一个组件“好不好用”,而不是“能不能用”。 这篇文章,我们不讲 API 清单、不背概念, 而是围绕 Props 系统的 5 个核心能力,一次性讲透 React 组件化的底层逻辑: Props 传递 Props 解构 默认值(defaultProps / 默认参数) 类型校验(PropTypes) children 插


设计模式和设计原则-中高级架构思路-面向接口编程
自由生长20242025/12/31

历史文章参见 设计模式-23种设计模式的说法-掘金 每日知识-设计模式-状态机模式-掘金 每日知识-设计模式-观察者模式 - 掘金 cpp笔记第3篇-C++多线程单例模式单例模式 - 掘金 今天讲讲面向接口编程的核心思想,它可以看到各种设计模式的一种杂糅。 面向接口编程的核心思想 以实际的代码举例子,我最近在写一个安卓的笔记程序,使用到了面向接口的编程方法,下面我以具体的类举例来说明面向接口编程的思想,以及后文解释,面向接口编程可以体现哪些设计模式。 一、依赖接口,而不是具体实现 // ❌ 面


HarmonyOS一杯冰美式的时间 -- FullScreenLaunchComponent
猫猫头啊2026/1/9

一、前言 最近在开发中,我们的元服务需要被其他应用通过FullScreenLaunchComponent拉起,我只能说当时上了5.0的当,FullScreenLaunchComponent也是Beta版本的!在实际开发中作为碰了几次灰,踩了不少坑,觉得有必要分享下,故有了此篇文章。 该系列依旧会带着大家,了解,开阔一些不怎么热门的API,也可能是偷偷被更新的API,也可以是好玩的,藏在官方文档的边边角角~当然也会有一些API,之前是我们辛辛苦苦的手撸代码,现在有一个API能帮我们快速实现的,希望


Monorepo入门
Hyyy2026/1/17

1. Monorepo 介绍 核心价值:把“需要一起演进的一组项目”放在同一个版本空间里,从而让跨项目改动(API 变更、重构、升级)能在一次提交里完成并验证 Monorepo 是把多个相关项目/包放在同一个 Git 仓库中管理的策略,有助于跨项目联动修改、内部包共享更顺畅、统一规范与 CI、版本控制、构建和部署等方面的复杂性,并提供更好的可重用性和协作性。 Monorepo 提倡了开放、透明、共享的组织文化,这种方法已经被很多大型公司广泛使用,如 Google、Facebook 和 Mic


Linux软件安装 —— Flink集群安装(集成Zookeeper、Hadoop高可用)
吱唔猪~2026/1/25

文章目录 一、节点说明二、配置节点间免密登录三、JDK安装四、Zookeeper安装五、Hadoop安装六、Flink安装1、基础环境准备(1)下载安装包(2)上传并解压 2、修改配置(1)配置zookeeper(2)配置flink-conf.yaml(3)配置workers(4)创建必要的目录(5)配置环境变量 3、分发flink 七、集群测试1、启动zookeeper,hadoop2、Yarn Session测试(1)模式介绍(2)准备测试资源

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客