进阶篇:Web 跨页面通信
Web 跨页面通信,指浏览器中不同浏览上下文之间的数据传递,包含:Tab 标签页、独立窗口、iframe 内嵌页面、新开弹窗等。
根据浏览器同源策略,可严格分为两大类场景:
- 同源跨页面通信:协议、域名、端口完全一致。
- 非同源跨页面通信:跨域通信,受浏览器安全策略限制。
同源跨页面通信方案
以下所有方案仅支持同源页面通信,无法跨域,是多 Tab、多窗口、iframe 同源数据同步的核心方案。
BroadcastChannel(广播通道)
核心原理:浏览器原生提供的同源全局命名广播通道。只要多个页面、Tab、iframe 使用完全相同的频道名称,即可加入同一个广播域,实现全员消息广播、全员监听。
关键特性:原生零依赖、专门用于多上下文广播、频道隔离、支持任意数据类型。
const bc = new BroadcastChannel('app-channel');
bc.onmessage = (e) => {
console.log('收到广播消息:', e.data);
};
bc.addEventListener('message', (e) => {
console.log('收到广播消息:', e.data);
});
bc.postMessage('Hello, 全局广播!');
bc.postMessage({
type: 'refresh',
msg: '页面数据刷新',
time: Date.now()
});
bc.close();
注意:
- 必须同名频道才能互通,不同频道完全隔离,互不干扰;
close()仅关闭当前页面的实例连接,不会销毁全局频道,其他页面可正常通信。
浏览器兼容性:全局支持率 95.77%,全面兼容 Chrome、Edge、Firefox、新版移动端浏览器;不支持 IE 全系、Safari 15.3 及以下、iOS 15.3 及以下。
优点:原生 API、使用极简、无中间层、支持多窗口、Tab 全局广播、数据格式无限制。
缺点:低版本 Safari 兼容差、仅支持同源通信。
Service Worker
核心原理:Service Worker 是独立于页面的后台代理进程,不受页面刷新、关闭影响。它可以获取当前作用域下所有打开的页面客户端,充当全局消息中转站,实现同源多页面统一广播。
// main.js
navigator.serviceWorker.register('/sw.js').then(() => {
console.log('Service Worker 注册成功')
})
navigator.serviceWorker.addEventListener('message', (e) => {
console.log('SW 全局广播消息:', e.data);
});
navigator.serviceWorker.controller?.postMessage('全局页面刷新通知');
// sw.js
self.addEventListener('message', (event) => {
console.log(`[Client]接收页面消息:${event.data}`)
event.waitUntil(
self.clients.matchAll().then((clients) => {
if (!clients || !clients.length) return
clients.forEach((client) => {
client.postMessage(event.data)
})
})
)
})
浏览器兼容性:全局支持率 96.22%,仅 IE 全系不支持,兼容性优于 BroadcastChannel,是生产级同源广播优选方案。
优点:兼容性极佳、后台常驻不依赖页面、支持离线能力、全局广播稳定。
缺点:需要单独注册、配置文件、存在生命周期管理成本。
LocalStorage(推荐)
核心原理:所有同源页面共享同一套 LocalStorage 存储。当 LocalStorage 数据发生增删改时,会触发全局 storage 事件,除当前修改页面外,所有同源页面均可监听捕获,实现跨页面同步。
window.addEventListener('storage', (e) => {
console.log(`[LocalStorage] 键名:${e.key},新值:${e.newValue},旧值:${e.oldValue}`)
})
window.localStorage.setItem('cross-msg', `页面同步消息-${Date.now()}`)
注意:
- 当前页面修改 LocalStorage,不会触发自身的 storage 事件;
- 新旧数据完全一致时,浏览器不会触发事件,必须拼接时间戳保证更新;
- LocalStorage 仅支持字符串存储,对象、数组需手动
JSON.stringify序列化; setItem、removeItem、clear均会触发全局变更事件。
优点:兼容性满分、零配置、开箱即用、无浏览器版本限制。
缺点:触发机制有局限、只能传字符串、高频同步性能较差。
SharedWorker(共享线程)
核心原理:SharedWorker 是浏览器多页面共享后台线程,区别于普通 Worker(单页面独占)。所有同源页面可共用同一个 SharedWorker,实现全局数据共享、消息互通,常驻后台不随页面刷新销毁。
// main.js
const sharedWorker = new SharedWorker('shared.worker.js', 'global-share')
sharedWorker.port.onmessage = (e) => {
console.log('[SharedWorker] 接收共享数据:', e.data)
}
sharedWorker.port.postMessage({
msg: '全局共享消息'
})
sharedWorker.port.postMessage({
get: true
})
// shared.worker.js
var shareData = null
self.addEventListener('connect', (e) => {
const port = e.ports[0]
port.addEventListener('message', (event) => {
if (event.data.get === true) {
port.postMessage(shareData)
} else {
shareData = event.data
}
})
port.start()
})
注意:
- 必须同一 worker 脚本文件才能实现共享,不同文件相互独立;
addEventListener监听必须手动执行port.start()启动端口;- 可通过
chrome://inspect/#workers调试共享线程代码。
浏览器兼容性:兼容性较差,IE、Safari、大部分移动端旧浏览器均不支持,业务场景极少使用。
优点:后台常驻、数据全局共享、刷新不丢失。
缺点:兼容性差、调试困难。
IndexedDB
原理与 LocalStorage 类似,属于浏览器端数据库级别的同源共享存储。
同源页面可读写同一 IndexedDB 数据库,通过页面可见事件、轮询、数据库更新事件实现跨页面数据同步。适合大容量、结构化、持久化数据共享,不适合轻量、高频的消息通信。
// 打开数据库
function openDB() {
return new Promise((resolve) => {
const req = indexedDB.open('CrossPageDB', 1);
req.onupgradeneeded = e => {
const db = e.target.result;
if (!db.objectStoreNames.contains('msgStore')) {
db.createObjectStore('msgStore', { keyPath: 'id' });
}
};
req.onsuccess = e => resolve(e.target.result);
})
}
// 写入消息
async function putMessage(msgObj) {
const db = await openDB();
const tx = db.transaction('msgStore', 'readwrite');
tx.objectStore('msgStore').put({ id: Date.now(), ...msgObj });
tx.commit();
db.close();
}
// 读取全部消息
async function getMessages() {
const db = await openDB();
const tx = db.transaction('msgStore', 'readonly');
const store = tx.objectStore('msgStore');
const res = await store.getAll();
db.close();
return res;
}
// 使用:页面 A 写入
putMessage({ content: '跨页面消息', from: 'pageA' });
// 页面 B 在 visibilitychange 触发时拉取最新数据
document.addEventListener('visibilitychange', async () => {
if (document.visibilityState === 'visible') {
const list = await getMessages();
console.log('IndexedDB 获取跨页面消息:', list);
}
})
备注:IndexedDB 本身没有原生消息事件,一般配合 visibilitychange 页面显示事件或定时器轮询,完成跨页面数据同步。
非同源跨页面通信方案
以下为前端标准跨域页面通信方案,支持不同域名、不同端口、不同协议页面互通。
window.postMessage
核心原理:浏览器官方唯一允许跨域窗口通信的原生 API,只要页面存在关联(window.open 弹窗、iframe 内嵌),即可实现安全双向跨域通信。
targetWindow.postMessage(message, targetOrigin, [transfer]);
- targetWindow:目标窗口引用(open 返回值、
iframe.contentWindow、window.opener)。 - message:支持任意类型数据,浏览器自动结构化克隆。
- targetOrigin:安全校验字段,精准限制目标域名,禁止随意使用
*。
open
// parent.js
var targetWindow = window.open('http://localhost:5300/child.html?type=5')
window.addEventListener('message', (e) => {
if (e.origin === 'http://localhost:5300') {
console.log(`[postMessage]接收:${e.data}`)
}
})
targetWindow.postMessage('Hello, 父页面消息!', 'http://localhost:5300')
// child.js
window.addEventListener('message', (e) => {
console.log(`[子页面]接收:${e.data}`)
})
window.opener.postMessage('子页面回执', 'http://localhost:5200')
iframe
父页面域名:http://a.com:5200,iframe 子页面域名:http://b.com:5300。
<!-- 父页面 a.html -->
<iframe id="crossIframe" src="http://b.com:5300/b.html"></iframe>
<script>
const iframe = document.querySelector('#crossIframe');
iframe.onload = function () {
iframe.contentWindow.postMessage(
{ type: 'notify', data: '父页面发给 iframe 的跨域消息' },
'http://b.com:5300'
);
};
window.addEventListener('message', (e) => {
if (e.origin === 'http://b.com:5300') {
console.log('父页面收到 iframe 消息:', e.data);
}
})
</script>
<!-- iframe 子页面 b.html -->
<script>
window.addEventListener('message', (e) => {
if (e.origin !== 'http://a.com:5200') return;
console.log('iframe 收到父页面消息:', e.data);
window.parent.postMessage(
{ type: 'reply', data: 'iframe 回执消息' },
'http://a.com:5200'
);
})
</script>
location.hash + iframe
核心原理:URL 中 hash(# 后面内容)的修改不会触发页面刷新,且支持跨域修改父/子页面 hash,配合 hashchange 事件实现跨域轻量通信。
限制:只能传递字符串,数据长度受 URL 长度限制;仅 hash 变化才触发事件。
父页面 http://a.com:5200/a.html,iframe 页面 http://b.com:5300/b.html:
<!-- a.html 父页面 -->
<iframe id="hashIframe" src="http://b.com:5300/b.html"></iframe>
<script>
const frame = document.querySelector('#hashIframe');
function sendToIframe(msg) {
frame.src = frame.src.split('#')[0] + `#${encodeURIComponent(msg)}`;
}
window.addEventListener('hashchange', () => {
const raw = decodeURIComponent(window.location.hash.slice(1));
console.log('父页面监听到 hash 变更,收到消息:', raw);
})
frame.onload = () => sendToIframe('HelloFromParent');
</script>
<!-- b.html iframe 子页面 -->
<script>
window.addEventListener('hashchange', () => {
const raw = decodeURIComponent(window.location.hash.slice(1));
console.log('iframe 收到父页面 hash 消息:', raw);
})
function sendToParent(msg) {
const base = 'http://a.com:5200/a.html';
window.parent.location.href = `${base}#${encodeURIComponent(msg)}`;
}
setTimeout(() => {
sendToParent('HelloFromIframe');
}, 1000);
</script>
注意:
- 仅 hash 值发生变化才会触发事件,重复相同值不会触发;
window.parent.location.href仅支持跨域赋值,不支持跨域取值;- 传递中文、特殊字符务必使用
encodeURIComponent、decodeURIComponent。
iframe + BroadcastChannel(终极方案)
实现原理:利用其中一个业务域名充当公共同源中转源,解决两个无关联跨域页面无法通信的问题。
- 同源页面直接使用
BroadcastChannel广播; - 跨域页面通过嵌入该域名下的隐藏中转 iframe,配合
postMessage接入广播。
页面关系:
- 页面 A(中转源域名):
https://a.com/pageA.html; - 页面 B(跨域页面):
https://b.com/pageB.html; - 中转页面(部署在 A 域名):
https://a.com/a-transfer.html。
业务页面 A:
<script>
const bc = new BroadcastChannel('cross-transfer-channel');
function sendMsg(data) {
bc.postMessage(data);
}
bc.onmessage = function(e) {
console.log('页面 A 接收跨域消息:', e.data);
}
sendMsg('Hello 我是页面 A');
</script>
跨域页面 B:
<iframe src="https://a.com/a-transfer.html" style="display: none;"></iframe>
<script>
window.addEventListener('message', (e) => {
if (e.origin === 'https://a.com') {
console.log('页面 B 接收广播消息:', e.data)
}
})
function sendMsgToAll(data) {
const iframe = document.querySelector('iframe')
iframe.contentWindow.postMessage({
type: 'broadcast',
payload: data
}, 'https://a.com')
}
sendMsgToAll('Hello 我是页面 B');
</script>
中转页面:
<script>
const bc = new BroadcastChannel('cross-transfer-channel');
// 接收跨域页面 B 的消息,全局广播
window.addEventListener('message', (e) => {
if (e.origin !== 'https://b.com') {
return
}
if (e.data.type === 'broadcast') {
bc.postMessage(e.data.payload)
}
})
// 接收全局广播,推送回当前跨域父页面
bc.onmessage = (e) => {
window.parent.postMessage(e.data, '*')
}
</script>
核心特点:
- 不需要独立中转域名,复用现有业务域名部署中转静态页面,减少运维成本。
- 实现无引用关系的跨域顶层页面广播通信,页面之间没有 window 引用也可以互通。
- 同源页面直接使用 BroadcastChannel,跨域页面仅嵌入隐藏 iframe 即可接入,新增跨域页面接入成本低。
- 纯前端实现,不依赖后端服务。
辅助通信方案
URL 参数传参
页面跳转、新开页面时通过 ?key=value 拼接参数传值。
特点:单向一次性通信,只能前页传后页,无法反向、无法实时同步。
WebSocket
依托服务端长连接作为消息中转中心,所有页面连接同一 WS 服务,由服务端统一广播消息。
特点:支持跨页面、跨域名、跨设备实时双向通信,适用于聊天、实时协作、大屏同步等场景。
通信方案整体分析
各方案核心特点
- BroadcastChannel:同源多窗口广播模式,代码简洁,兼容性中等,低版本 Safari 不支持;
- Service Worker:同源全局广播,兼容性最好,稳定可靠;
- LocalStorage:同源广播,兼容性满分,有触发限制;
- Shared Worker:同源数据共享模式,需主动取值,兼容性差;
- IndexedDB:同源大容量数据共享,适合持久化存储,无原生消息事件;
- postMessage:通用跨域通信,支持关联窗口、iframe,兼容性极佳;
- location.hash + iframe:轻量跨域通信,适合简单字符串状态同步,受 URL 长度限制;
- 跨域 iframe + BroadcastChannel 中转:解决无关联顶层页面跨域广播,需在同源域名部署中转静态页;
- URL 参数、WebSocket:特殊场景补充方案。
数据同步触发方式
- 事件触发:BroadcastChannel、storage、message、hashchange;
- 主动轮询:定时器轮询存储、数据库;
- 页面生命周期触发:visibilitychange、pageshow 页面显示刷新数据。
总结
- 同一窗口同源页面通信:优先 LocalStorage;
- 多 Tab、多窗口同源全局同步:优先 BroadcastChannel、Service Worker;
- 弹窗登录、跨窗口授权回传:postMessage + window.open;
- iframe 父子跨域通信:postMessage、
location.hash; - 两个无关联顶层页面跨域广播:iframe 中转 + BroadcastChannel。