BroadcastChannel
接口代理了一个命名频道,能够实现同源下浏览器的不一样窗口,标签页,frame或者iframe下的浏览器上下文(一般是同一个网站下不一样的页面)之间的简单通讯。javascript
经过建立一个监听某个频道下的BroadcastChannel
对象,你能够接收发送给该频道的全部消息。不一样页面能够经过构造BroadcastChannel
来订阅相同的频道,而后相互之间即可以进行全双工(双向)通讯。css
咱们能够经过建立两个页面,而后在浏览器的不一样标签页分别访问这两个页面,来演示如何使用BroadcastChannel
通讯。html
sender.htmljava
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Receiver 1</title>
<style> body { border: 1px solid black; padding: .5rem; height: 150px; font-family: "Fira Sans", sans-serif; } h1 { font: 1.6em "Fira Sans", sans-serif; margin-bottom: 1rem; } textarea { padding: .2rem; } label, br { margin: .5rem 0; } button { vertical-align: top; height: 1.5rem; } </style>
</head>
<body>
<div>
<h1>发送者</h1>
<label for="message">输入要广播的信息:</label><br/>
<textarea id="message" name="message" rows="1" cols="40">Hello</textarea>
<button id="broadcast-message" type="button">开始广播</button>
</div>
<script> const channel = new BroadcastChannel('example-channel'); const messageControl = document.querySelector('#message'); const broadcastMessageButton = document.querySelector('#broadcast-message'); broadcastMessageButton.addEventListener('click', () => { channel.postMessage(messageControl.value); }); </script>
</body>
</html>
复制代码
receiver.html浏览器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Receiver</title>
<style> h1 { margin-bottom: 1rem; } </style>
</head>
<body>
<div>
<h1>接收者</h1>
<div id="received"></div>
</div>
<script> const channel = new BroadcastChannel('example-channel'); channel.addEventListener('message', (event) => { received.textContent = event.data; }); </script>
</body>
</html>
复制代码
点击发送页面的“开始广播”按钮,接收页面将收到消息并展现到div
上。函数
BroadcastChannel
接口BroadcastChannel
继承自EventTarget
,是基于标准的事件模型实现的。post
BroadcastChannel
接口很是简单。经过建立一个BroadcastChannel
对象,一个客户端就加入了某个指定的频道。只须要向构造函数传入一个参数:频道名称。若是这是首次链接到该广播频道,相应资源会自动被建立。网站
// 链接到广播频道
var bc = new BroadcastChannel('test_channel');
复制代码
如今发送消息就很简单了,只须要调用BroadcastChannel
对象上的postMessage()
方法便可。该方法的参数能够是任意对象。最简单的例子就是发送字符串文本消息:ui
// 发送简单消息的示例
bc.postMessage('This is a test message.');
复制代码
当消息被发送以后,全部链接到该频道的BroadcastChannel
对象上都会触发message
事件。该事件没有默认的行为,可是能够使用onmessage
定义一个函数来处理消息。this
// 简单示例,用于将事件打印到控制台
bc.onmessage = function (ev) { console.log(ev); }
复制代码
经过调用BroadcastChannel
对象的close()
方法,能够离开频道。这将断开该对象和其关联的频道之间的联系,并容许它被垃圾回收。
// 断开频道链接
bc.close();
复制代码
既然BroadcastChannel
继承自EventTarget
,那么咱们就先实现EventTarget
,这里直接使用MDN上的简单实现。
class EventTarget {
private readonly listeners: {
[index: string]: Array<TListener>,
};
constructor() {
this.listeners = {};
}
addEventListener(type: string, callback: TListener): void {
if (!(type in this.listeners)) {
this.listeners[type] = [];
}
this.listeners[type].push(callback);
}
removeEventListener(type: string, callback: TListener): void {
if (!(type in this.listeners)) {
return;
}
var stack = this.listeners[type];
for (var i = 0, l = stack.length; i < l; i++) {
if (stack[i] === callback) {
stack.splice(i, 1);
return this.removeEventListener(type, callback);
}
}
}
dispatchEvent(event: TEvent): void {
if (!(event.type in this.listeners)) {
return;
}
var stack = this.listeners[event.type];
event.target = this;
for (var i = 0, l = stack.length; i < l; i++) {
stack[i].call(this, event);
}
};
}
复制代码
BroadcastChannel
对象。const channels: {
[index: string]: Set<BroadcastChannel>,
} = {};
复制代码
为了简化操做,咱们直接使用了Set
代替Array
来存储BroadcastChannel
对象。
BroadcastChannel
类,继承自EventTarget
类。class BroadcastChannel extends EventTarget{
public readonly channel: string;
public onmessage?: (message: TMessage) => any;
private readonly onMessageEventHandler: (event: TEvent) => void;
}
复制代码
注意,这里除了channel
和onmessage
这两个公共属性以外,还额外定义了一个onMessageEventHandler
私有属性,接下来咱们便会用到它们。
constructor(channel: string) {
super();
const that = this;
this.channel = channel;
this.onMessageEventHandler = function onMessageEventHandler(e: TEvent) {
if (that.onmessage) {
that.onmessage({
type: 'message',
data: e.detail,
});
}
};
this.addEventListener('message', this.onMessageEventHandler);
if (!channels[channel]) channels[channel] = new Set();
channels[channel].add(this);
}
复制代码
在构建函数中,监听了'message'事件,并在事件回调中执行onmessage
注册的函数。同时将BroadcastChannel
实例对象注册到频道中心,以便后续广播消息到该BroadcastChannel
实例。
postMessage
方法。postMessage(message: any) {
for (let broadcastChannel of channels[this.channel]) {
if (broadcastChannel === this) continue; // 不要发给本身,以避免形成广播风暴
broadcastChannel.dispatchEvent({
type: 'message',
detail: message,
});
}
}
复制代码
从频道中心遍历订阅了指定channel
的全部BroadcastChannel
对象,依次调用其dispatchEvent
方法,达到广播消息的目的。
close
方法,移除对message
事件的监听并从频道中心删除。close() {
this.removeEventListener('message', this.onMessageEventHandler);
channels[this.channel].delete(this);
if (channels[this.channel].size === 0) {
delete channels[this.channel];
}
}
复制代码
BroadcastChannel
的规范来实现的话,消息是要序列化和反序列化的,由于不一样的浏览器上下文之间没法共享内存引用,只能序列化以后才能传输,本文的实现省略了这一步;BroadcastChannel
是基于浏览器上下文进行隔离的,同一个上下文内部的不一样BroadcastChannel
对象相互之间是不通讯的,本文的实现简化成了实例之间的隔离;