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
對象相互之間是不通訊的,本文的實現簡化成了實例之間的隔離;