前言:
大家在做项目时总会用到一些框架,比如无界微前端应用通信,vue全局事件总线等等。这些框架的通信原理或多或少都有用到发布订阅的思想,面试官很有可能会让你手写一个简单的发布订阅模型。
(笔者之前在介绍自己的微前端项目时就被要求手写一个发布订阅,结果现场草草写出来的代码有很多瑕疵qaq~)
介绍:
什么是发布订阅模型?
官方定义:发布订阅模型又叫观察者模型,他定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知。
说人话!!!
说人话:其实我们js应用里有很多发布订阅的例子,他们的共性就是我们可以给一些模型上绑定一些事件(回调函数),当对应的事件触发时,系统会自动执行我们的回调函数(相当于通知我们)
比如:给一个dom事件绑定点击事件
// 相当于订阅
document.body.addEventListener('click',function(){
console.log('我被点击啦')
})
// 相当于发布
document.body.click() //模拟用户点击
再比如:ajax中的success事件.......
实战-手写发布订阅
在了解了发布订阅的基本概念之后,我们来尝试手写一个发布订阅模型:
首先:我们要知道发布订阅肯定是有三个要素的:
- 发布者发布事件
- 订阅者订阅事件
- 订阅者取消事件
因此,我们手写发布订阅模型的主要精力应该放在实现这三个方法上
手写发布订阅模型
我们先来定义一下发布订阅模型
这个对象有三个方法:
- listen:订阅事件
- trigger:发布事件
- remove:取消事件
这个对象有一个属性:
- clientlist:用于存放订阅事件时的回调函数
基础比较好的同学可以直接阅读下面的代码,发布订阅的思想并不难,相信你可以理解绝大多数内容
如果感觉比较难理解,可以继续阅读笔者下面的解释
var event = {
clientList: {},
listen: function (key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn);
},
trigger: function () {
var key = Array.prototype.shift.call(arguments), // (1);
fns = this.clientList[key];
if (!fns || fns.length === 0) { // 如果没有绑定对应的消息
return false;
}
for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments);
}
},
remove: function (key, fn) {
var fns = this.clientList[key];
if (!fns) { // 如果key 对应的消息没有被人订阅,则直接返回
return false;
}
if (!fn) { // 如果没有传入具体的回调函数,表示需要取消key 对应消息的所有订阅
fns && (fns.length = 0);
} else {
for (var l = fns.length - 1; l >= 0; l--) { // 反向遍历订阅的回调函数列表
var _fn = fns[l];
if (_fn === fn) {
fns.splice(l, 1); // 删除订阅者的回调函数
}
}
}
}
};
解释发布订阅模型
基本思想:上面的代码基本思想就是,提供一个clientlist的对象结构,这个对象用于存放订阅事件的回调函数。那有同学可能会问了,这个结构为什么是一个对象不是一个数组呢? 因为发布订阅我们要区分不同的事件,所有该对象每一个key对应一个数组,数组里存放的是相同事件的回调函数。
-
listen函数:在订阅事件时需要传入订阅事件名(key),和回调函数(通知你的函数),给clientlist对象的key对应的数组push该回调函数即可
-
trigger函数:当发布事件时,执行key对应数组内的所有回调函数
-
remove函数:传入key和回调函数,从数组内删除该函数
总结
我们可以把我们的发布订阅模型和常见主流框架对比一下,我们可以发现使用是一致的,这可以证明我们总结的发布订阅原理是没有问题的。
无界eventbus通信:
vue eventbus通信:
参考资料
无界微前端官网:wujie-micro.github.io/doc/guide/c…
vue2官网:v2.cn.vuejs.org/v2/api/#vm-…