大家在做后台管理、IM 实时通知、多窗口同步状态时,一定遇到过这个场景:同一个域名开两个 Tab,怎么让一个页面实时把消息传给另一个页面?
很多人只知道 postMessage,其实真正好用、工程上常用的反而是另外几种。今天一次性把 4 种同源 Tab 通信方案讲透:原理 + 代码 + 优缺点 + 使用场景,看完直接能写到项目里。
一、最兼容最简单:localStorage storage 事件
这是兼容性最好、几乎零成本 的跨 Tab 通信方式,利用浏览器 storage 事件机制。
原理: 当同源页面修改 localStorage 时,其他同域页面会触发 storage 事件,但当前页面自己不会触发。
代码示例
js
// Tab A 发送
localStorage.setItem('tabMsg', JSON.stringify({
type: 'refresh',
data: '用户信息已更新'
}));
// Tab B 监听
window.addEventListener('storage', (e) => {
if (e.key !== 'tabMsg') return;
const msg = JSON.parse(e.newValue);
console.log('收到消息:', msg);
});
特点:
- 只支持字符串,必须
JSON序列化 - 自身不触发,天然适合跨
Tab - 兼容性拉满,
IE也能用
缺点:
- 频繁
setItem可能冲突,不适合高频通信
二、现代浏览器首选:BroadcastChannel API
如果你的项目不需要兼容老 IE,强烈推荐这个,专门用于同源上下文通信。
原理: 创建一个命名频道,所有同源页面加入该频道,即可互相收发消息。
代码示例
js
// 发送方
const channel = new BroadcastChannel('my-app');
channel.postMessage({
cmd: 'logout',
msg: '账号在别处登录'
});
// 接收方
const channel = new BroadcastChannel('my-app');
channel.onmessage = (e) => {
console.log(e.data);
};
// 用完记得关闭
// channel.close();
特点:
- 支持对象、数组等结构化数据
API简洁、语义清晰- 适合多
Tab同步、全局通知、状态同步 - 兼容性:
Chrome 54+ / Edge 79+ / Firefox 38+
三、有窗口引用:window.postMessage
如果你是通过 window.open 打开的新 Tab,有明确窗口引用,可以用这种。
原理: 通过目标窗口对象,安全地跨源 / 同源发送消息,同源下更简单。
代码示例
js
// Tab A 打开 Tab B
const tabB = window.open('/b.html');
tabB.postMessage('hello', location.origin);
// Tab B 接收
window.addEventListener('message', (e) => {
// 同源校验
if (e.origin !== location.origin) return;
console.log(e.data);
});
特点:
- 必须持有目标窗口对象
- 适合父子窗口、弹窗页面
- 可跨源,但同源更安全
四、高级方案:Service Worker 中转
适合复杂 PWA、离线应用、需要统一管理所有页面的场景。
原理: Service Worker 作为全局中间人,接收某一页面消息,转发给其他所有 Tab。
核心代码
js
// 页面发送
navigator.serviceWorker.controller.postMessage({
type: 'BROADCAST',
content: '来自某 Tab 的消息'
});
// sw.js 中转
self.addEventListener('message', async (e) => {
const clients = await self.clients.matchAll();
clients.forEach(client => {
if (client.id !== e.source.id) {
client.postMessage(e.data);
}
});
});
// 页面接收
navigator.serviceWorker.addEventListener('message', (e) => {
console.log(e.data);
});
特点:
- 支持离线、后台运行
- 可做全局消息中心
- 接入成本稍高,适合中大型项目
五、方案对比(直接收藏这张表)
| 方案 | 难度 | 兼容性 | 数据类型 | 适用场景 |
|---|---|---|---|---|
| localStorage | ⭐ | 极高 | 字符串 | 简单同步、兼容老项目 |
| BroadcastChannel | ⭐⭐ | 高 | 结构化 | 现代浏览器、多 Tab 通知 |
| postMessage | ⭐⭐ | 高 | 结构化 | 窗口有引用关系 |
| Service Worker | ⭐⭐⭐ | 中 | 结构化 | PWA、离线、复杂全局消息 |
六、实际业务推荐
- 后台管理系统多
Tab同步登录台 →BroadcastChannel - 兼容
IE/ 老旧项目 →localStorage - 弹窗操作后刷新列表 →
postMessage PWA、离线消息、多端统一 →Service Worker
七、4 套方案合一工具类(直接复制即用)
完整封装代码(可直接放项目 utils)
js
/**
* 同源多 Tab 跨页面通信工具类
* 支持:BroadcastChannel / localStorage / postMessage / ServiceWorker
*/
class TabMessage {
/**
* @param {Object} options
* @param {string} options.key - 通信频道名 / storage 键名
* @param {'broadcast' | 'storage' | 'postMessage' | 'serviceWorker'} [options.mode='broadcast']
* @param {Window | null} [options.targetWindow=null] - postMessage 模式用的目标窗口
* @param {string} [options.targetOrigin=location.origin] - postMessage 可信域
*/
constructor(options) {
this.key = options.key;
this.mode = options.mode || 'broadcast';
this.targetWindow = options.targetWindow || null;
this.targetOrigin = options.targetOrigin || location.origin;
this.channel = null; // BroadcastChannel
this.listener = null; // 外部消息回调
this.boundHandler = null;
this.init();
}
init() {
switch (this.mode) {
case 'broadcast':
this.initBroadcast();
break;
case 'storage':
this.initStorage();
break;
case 'postMessage':
this.initPostMessage();
break;
case 'serviceWorker':
this.initServiceWorker();
break;
}
}
// BroadcastChannel 模式
initBroadcast() {
this.channel = new BroadcastChannel(this.key);
this.channel.onmessage = (e) => {
this.listener?.(e.data);
};
}
// localStorage 模式
initStorage() {
this.boundHandler = (e) => {
if (e.key !== this.key) return;
try {
const data = JSON.parse(e.newValue);
this.listener?.(data);
} catch {}
};
window.addEventListener('storage', this.boundHandler);
}
// postMessage 模式
initPostMessage() {
this.boundHandler = (e) => {
if (e.origin !== this.targetOrigin) return;
this.listener?.(e.data);
};
window.addEventListener('message', this.boundHandler);
}
// ServiceWorker 模式
initServiceWorker() {
if (!navigator.serviceWorker?.controller) return;
this.boundHandler = (e) => {
this.listener?.(e.data);
};
navigator.serviceWorker.addEventListener('message', this.boundHandler);
}
/**
* 发送消息
* @param {any} data
*/
send(data) {
switch (this.mode) {
case 'broadcast':
this.channel?.postMessage(data);
break;
case 'storage':
localStorage.setItem(this.key, JSON.stringify(data));
break;
case 'postMessage':
this.targetWindow?.postMessage(data, this.targetOrigin);
break;
case 'serviceWorker':
navigator.serviceWorker.controller?.postMessage({
type: 'TAB_BROADCAST',
data,
});
break;
}
}
/**
* 监听消息
* @param {(data: any) => void} callback
*/
onMessage(callback) {
this.listener = callback;
}
/**
* 销毁监听,避免内存泄漏
*/
destroy() {
switch (this.mode) {
case 'broadcast':
this.channel?.close();
break;
case 'storage':
window.removeEventListener('storage', this.boundHandler);
break;
case 'postMessage':
window.removeEventListener('message', this.boundHandler);
break;
case 'serviceWorker':
navigator.serviceWorker?.removeEventListener('message', this.boundHandler);
break;
}
this.listener = null;
}
}
export default TabMessage;
7.1、Service Worker 配套中转代码(sw.js)
如果你要用 serviceWorker 模式,在你的 sw 里加上这段即可:
js
// sw.js
self.addEventListener('message', async (event) => {
if (event.data?.type !== 'TAB_BROADCAST') return;
const allClients = await self.clients.matchAll({ type: 'window' });
for (const client of allClients) {
// 不发给自己
if (client.id !== event.source.id) {
client.postMessage(event.data.data);
}
}
});
7.2、四种模式真实业务用法
- 现代项目首选:
BroadcastChannel(推荐)
js
const tabMsg = new TabMessage({
key: 'app-notice',
mode: 'broadcast',
});
tabMsg.onMessage((data) => {
if (data.type === 'logout') location.href = '/login';
});
// 某个页面登出
tabMsg.send({ type: 'logout' });
- 兼容
IE:localStorage模式
js
const tabMsg = new TabMessage({
key: 'tab-storage-msg',
mode: 'storage',
});
- 弹窗 / 新开窗口:
postMessage
js
// 页面 A
const winB = window.open('/pageB');
const tabMsg = new TabMessage({
mode: 'postMessage',
targetWindow: winB,
});
// 页面 B 直接监听即可
PWA/ 复杂全局消息:ServiceWorker
js
const tabMsg = new TabMessage({
mode: 'serviceWorker',
});
总结:
实际开发中,我一般直接把这个工具类丢进 utils/tabMessage.js,多 Tab 同步登录态、刷新列表、全局通知、异地登出全都靠它。
四种模式一套 API 抹平差异,再也不用写一堆重复监听逻辑。项目里遇到跨 Tab 通信,复制粘贴这篇就够了。
⭐各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!⭐