日常开发做项目时,如果采用非Vue脚手架时,肯定会碰到这样的场景:比如在浏览器中新开两个tab页面,A页面发送消息后,B页面实时监听并触发某些动作。类似的需求有很多,比如实时共享状态等等。这样的实时通信场景的解决方案我相信大家有很多想法,比如localStorage、postMessage、WebSocket、sharedWorker等等。
今天带来另一种方式:BroadcastChannel广播通信。如果着急,可直接跳转到“项目中简单封装章节查看封装代码”
BroadcastChannel 接口代理了一个命名频道,可以让指定 origin 下的任意 browsing context 来订阅它。它允许同源的不同浏览器窗口,Tab 页,frame 或者 iframe 下的不同文档之间相互通信。通过触发一个 message 事件,消息可以广播到所有监听了该频道的 BroadcastChannel 对象。
简单来说,就是需要在同源的情况下,实现浏览器多窗口实时进行通信,且该通信是广播进行的。
// 通道名称,用以区分不同的通道。对于相同的来源下的所有浏览上下文,一个名称只对应一个通道。是string类型,用来标识当前的BroadcastChannel
const channel = new BroadcastChannel(channelName)
可以使用postMessage() 发送一条消息,给所有同源下监听了该频道的所有浏览器。消息message事件的形式发送给每一个绑定到该频道的广播频道。
const channel = new BroadcastChannel("test")
// 发送消息通知,参数是任何对象
bc.postMessage('hello xiaozong');
发送事件后,如何使用进行消息的监听呢?当频道收到一条消息时,会在关联的BroadcastChannel对象上触发 message 事件,监听方式有两种,具体如下:
const channel = new BroadcastChannel("test")
// 消息监听 方式一
channel.onmessage = ({data}) => {
// 这里写具体的业务逻辑
}
// 消息监听 方式二
channel.addEventListener('message', ({data}) => {
// 这里写具体的业务逻辑
})
const channel = new BroadcastChannel('test');
// 方式一
channel.addEventListener('messageerror', ({data}) => {
console.error(data);
})
// 方式二
channel.onmessageerror = ({data}) => {
console.log(data);
};
通过调用 close() 方法,可以马上断开其与对应频道的关联,并让其被垃圾回收。这是必要的步骤,因为浏览器没有其他方式知道频道不再被需要。不断开可能会导致一直处于监听状态,消耗资源,会导致不能被内存回收。
// 连接到指定频道
const channel = new BroadcastChannel('test');
// 当完成后,断开与频道的连接
channel.close();
/**
* 简单封装BroadcastChannel的用法
*/
const Channel = {
/**
* BroadcastChannel对象
*/
channel: null,
/**
* 实例化BroadcastChannel对象,赋值给channel变量
* @param {*} channelName 通道名称,用以区分不同的通道
* @returns
*/
getChannel: (channelName) => {
Channel.channel = new BroadcastChannel(channelName)
return Channel.channel
},
/**
* 发送消息
* @param {*} object 消息体
*/
send: (object) => {
Channel.channel.postMessage(object)
},
/**
* 发送消息,重载方法,可直接调用,省略对象实例化操作
* @param {*} channelName 通道名称,用以区分不同的通道
* @param {*} object 消息体
*/
send: (channelName, object) => {
if (Channel.channel == null) {
Channel.channel = Channel.getChannel(channelName)
}
Channel.channel.postMessage(object)
},
/**
* 监听消息
* @param {*} callback 回调函数
*/
listen: (callback) => {
Channel.channel.onmessage = ({ data }) => {
callback(data)
}
},
/**
* 监听消息,重载方法,可直接调用,省略对象实例化操作
* @param {*} channelName 通道名称,用以区分不同的通道
* @param {*} callback 回调函数
*/
listen: (channelName, callback) => {
if (Channel.channel == null) {
Channel.channel = Channel.getChannel(channelName)
}
Channel.channel.onmessage = ({ data }) => {
callback(data)
}
},
/**
* 通道关闭
*/
close: () => {
Channel.channel.close()
},
/**
* 通道关闭,重载方法,可直接调用,省略对象实例化操作
* @param {*} channelName 通道名称,用以区分不同的通道
*/
close: (channelName) => {
if (Channel.channel == null) {
Channel.channel = Channel.getChannel(channelName)
}
Channel.channel.close()
},
/**
* 通道枚举,定义业务中需要用到的所有通道名称枚举,可根据业务需求无限扩容
*/
channelEnum: {
TEST: { name: 'test', coment: '测试通道' },
REAL_EVENT: { name: 'real_event', coment: '实时事项通道' },
}
}
使用方式测试如下:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>标签页通信-发送端title>
<script src="./channel.js">script>
head>
<body>
标签页通信-发送端
body>
<script>
Channel.send(Channel.channelEnum.TEST.name, 'hello xiaozong')
script>
html>
使用方式测试如下:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>标签页通信-接收端title>
<script src="./channel.js">script>
head>
<body>
标签页通信-接收端
body>
<script>
Channel.listen(Channel.channelEnum.TEST.name, (data) => {
document.write(`${data}`)
})
script>
html>
昨晚十二点左右心血来潮,太晚了也只进行了简单的封装,基本满足常规场景的使用,如果实际项目开发中有用到的话,可以优化后使用。