同源多页面实时通信之BroadcastChannel实现及简单封装

背景

日常开发做项目时,如果采用非Vue脚手架时,肯定会碰到这样的场景:比如在浏览器中新开两个tab页面,A页面发送消息后,B页面实时监听并触发某些动作。类似的需求有很多,比如实时共享状态等等。这样的实时通信场景的解决方案我相信大家有很多想法,比如localStoragepostMessageWebSocketsharedWorker等等。
今天带来另一种方式:BroadcastChannel广播通信。如果着急,可直接跳转到“项目中简单封装章节查看封装代码”

介绍及API使用

是什么

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();

项目中简单封装

channel.js文件,引入到使用的页面中

/**
 * 简单封装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: '实时事项通道' },
    }
}

发送端send.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.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(`<p>${data}</p>`)
    })
</script>

</html>

运行测试一下吧

就只贴一下图吧 ,比较简单
在这里插入图片描述
在这里插入图片描述

浏览器兼容程度

在这里插入图片描述

总结

昨晚十二点左右心血来潮,太晚了也只进行了简单的封装,基本满足常规场景的使用,如果实际项目开发中有用到的话,可以优化后使用。

重要更新

今天发现这样封装一个页面只支持创建一个channel实例,这完全不行,于是进行了修改,现在附在下面,很抱歉。

/**
 * 简单封装BroadcastChannel的用法
 */
const Channel = {
    /**
     * BroadcastChannel对象Map
     */
    channelMap: new Map(),

    /**
     * 发送消息,重载方法,可直接调用,省略对象实例化操作
     * @param {*} channelName 通道名称,用以区分不同的通道
     * @param {*} object 消息体
     */
    send: (channelName, object) => {
        if (!Channel.channelMap.has(channelName)) {
            let channel = new BroadcastChannel(channelName);
            Channel.channelMap.set(channelName, channel);
        }
        Channel.channelMap.get(channelName).postMessage(object);
    },

    /**
     * 监听消息,重载方法,可直接调用,省略对象实例化操作
     * @param {*} channelName 通道名称,用以区分不同的通道
     * @param {*} callback 回调函数
     */
    listen: (channelName, callback) => {
        if (!Channel.channelMap.has(channelName)) {
            let channel = new BroadcastChannel(channelName);
            Channel.channelMap.set(channelName, channel);
        }
        Channel.channelMap.get(channelName).onmessage = ({ data }) => {
            callback(data);
        };
    },

    /**
     * 通道关闭
     * @param {*} channelName 通道名称,用以区分不同的通道
     */
    close: (channelName) => {
        if (Channel.channelMap.has(channelName)) {
            Channel.channelMap.get(channelName).close();
            Channel.channelMap.delete(channelName);
        }
    },

    /**
     * 通道枚举,定义业务中需要用到的所有通道名称枚举,可根据业务需求无限扩容
     */
    channelEnum: {
        REMOVE_TAB: { name: 'removeTab', comment: 'tabs标签移除时,用以通知被关闭页面,进行诸如实例、注册事件等的销毁工作' },
        GLOBAL_LOADING: { name: 'globalLoading', comment: '全局加载动画' },
    },
};

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是小宗啊?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值