BroadcastChannel API 允许同源脚本向其他浏览上下文发送消息。它可以被视为一个简单的消息总线,可在窗口/标签页、iframe、Web Worker 和服务工作线程之间实现发布/订阅语义。
API 基础知识
Broadcast Channel API 是一个简单的 API,可让您更轻松地在浏览上下文之间进行通信。也就是说,在窗口/标签页、iframe、Web Worker 和服务工作线程之间进行通信。发布到给定渠道的消息会传送给该渠道的所有监听器。
BroadcastChannel
构造函数接受一个参数:渠道的名称。此名称用于标识频道,并在各种浏览情境中保持不变。
// Connect to the channel named "my_bus".
const channel = new BroadcastChannel('my_bus');
// Send a message on "my_bus".
channel.postMessage('This is a test message.');
// Listen for messages on "my_bus".
channel.onmessage = function(e) {
console.log('Received', e.data);
};
// Close the channel when you're done.
channel.close();
发送消息
消息可以是字符串,也可以是结构化克隆算法支持的任何内容(字符串、对象、数组、Blob、ArrayBuffer、Map)。
示例 - 发送 Blob 或 File
channel.postMessage(new Blob(['foo', 'bar'], {type: 'plain/text'}));
频道不会向自己广播。因此,如果您在同一网页上同时有 onmessage
监听器和指向同一渠道的 postMessage()
,则该 message
事件不会触发。
与其他技术的区别
至此,您可能想知道这与其他消息传递技术(例如 WebSocket、SharedWorker、MessageChannel
API 和 window.postMessage()
)有何关联。Broadcast Channel API 不会取代这些 API。每种方法都有各自的用途。Broadcast Channel API 旨在让同源脚本之间轻松实现一对多通信。
广播通道的部分用例:
- 检测其他标签页中的用户操作
- 了解用户何时在其他窗口/标签页中登录账号。
- 指示工作器执行一些后台工作
- 了解服务何时完成某项操作。
- 当用户在一个窗口中上传照片时,将其传递到其他打开的页面。
示例:网页知道用户何时退出(即使是从同一网站的其他打开的标签页退出):
<button id="logout">Logout</button>
<script>
function doLogout() {
// update the UI login state for this page.
}
const authChannel = new BroadcastChannel('auth');
const button = document.querySelector('#logout');
button.addEventListener('click', e => {
// A channel won't broadcast to itself so we invoke doLogout()
// manually on this page.
doLogout();
authChannel.postMessage({cmd: 'logout', user: 'Eric Bidelman'});
});
authChannel.onmessage = function(e) {
if (e.data.cmd === 'logout') {
doLogout();
}
};
</script>
再举一个例子,假设您想在用户更改应用中的“离线存储设置”后,指示服务工件移除缓存的内容。您可以使用 window.caches
删除其缓存,但服务工件可能已经包含用于执行此操作的实用程序。我们可以使用 Broadcast Channel API 重复使用该代码!如果没有 Broadcast Channel API,您必须循环处理 self.clients.matchAll()
的结果,并对每个客户端调用 postMessage()
,以便实现从服务工件到其所有客户端的通信(实现此功能的实际代码)。使用广播渠道会将此值设为 O(1)
,而不是 O(N)
。
示例:指示服务工作器移除缓存,并重复使用其内部实用程序方法。
在 index.html 中
const channel = new BroadcastChannel('app-channel');
channel.onmessage = function(e) {
if (e.data.action === 'clearcache') {
console.log('Cache removed:', e.data.removed);
}
};
const messageChannel = new MessageChannel();
// Send the service worker a message to clear the cache.
// We can't use a BroadcastChannel for this because the
// service worker may need to be woken up. MessageChannels do that.
navigator.serviceWorker.controller.postMessage({
action: 'clearcache',
cacheName: 'v1-cache'
}, [messageChannel.port2]);
在 sw.js 中
function nukeCache(cacheName) {
return caches.delete(cacheName).then(removed => {
// ...do more stuff (internal) to this service worker...
return removed;
});
}
self.onmessage = function(e) {
const action = e.data.action;
const cacheName = e.data.cacheName;
if (action === 'clearcache') {
nukeCache(cacheName).then(removed => {
// Send the main page a response via the BroadcastChannel API.
// We could also use e.ports[0].postMessage(), but the benefit
// of responding with the BroadcastChannel API is that other
// subscribers may be listening.
const channel = new BroadcastChannel('app-channel');
channel.postMessage({action, removed});
});
}
};
与 postMessage()
的区别
与 postMessage()
不同,您不再需要保留对 iframe 或 worker 的引用,即可与其通信:
// Don't have to save references to window objects.
const popup = window.open('https://another-origin.com', ...);
popup.postMessage('Sup popup!', 'https://another-origin.com');
window.postMessage()
还允许您跨源进行通信。Broadcast Channel API 是同源的。由于消息保证来自同一来源,因此无需像以前使用 window.postMessage()
那样对其进行验证:
// Don't have to validate the origin of a message.
const iframe = document.querySelector('iframe');
iframe.contentWindow.onmessage = function(e) {
if (e.origin !== 'https://expected-origin.com') {
return;
}
e.source.postMessage('Ack!', e.origin);
};
只需“订阅”特定渠道,即可进行安全的双向通信!
与 SharedWorker 的区别
对于需要向可能多个窗口/标签页或工作器发送消息的简单情况,请使用 BroadcastChannel
。
对于管理锁、共享状态、在服务器和多个客户端之间同步资源或与远程主机共享 WebSocket 连接等更复杂的用例,共享 worker 是最合适的解决方案。
与 MessageChannel API 的区别
Channel Messaging API 和 BroadcastChannel
之间的主要区别在于,后者是一种向多个监听器分派消息的方式(一对多)。MessageChannel
适用于脚本之间直接的一对一通信。这种方式也更复杂,需要您在每个端设置有端口的通道。
功能检测和浏览器支持
目前,Chrome 54、Firefox 38 和 Opera 41 支持 Broadcast Channel API。
if ('BroadcastChannel' in self) {
// BroadcastChannel API supported!
}
至于 polyfill,目前有以下几种:
- https://gist.github.com/alexis89x/041a8e20a9193f3c47fb
- https://gist.github.com/inexorabletash/52f437d1451d12145264
我没有尝试过这些方法,因此您的结果可能会有所不同。