发布订阅是什么?
发布订阅模式又叫观察者模式,它定义对象间的一种 一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象豆浆得到通知。在javascript开发中,一般用事件模型来替代传统的发布-订阅模式。
通过事件监听的方式将需要异步执行的事件放入一个事件队列,等时机后统一执行
实现发布订阅通常的数据结构为
const eventLoop = {
key1:[fn1,fn2,fn3], // 事件key1,订阅额事件fn1,fn2.fn3先放入队列
key2:[fn2,fn3,fn4], // 事件key2,订阅额事件fn2,fn3.fn4先放入队列
....
}
// 事件发布的事件,遍历eventLoop['event1']中的所有事件进行调用
复制代码
创建一个简单的发布订阅模式
- 首先要指定好谁充当发布者(比如售楼处);
- 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者(售楼处的花名册)
- 最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数(遍历整个花名册,挨个发短信)
var salesOffices = {
clientList: [],
listen(fn) {
this.clientList.push(fn)
},
trigger() {
for (let i = 0, fn; fn = this.clientList[i++];) {
fn.apply(this, arguments);
}
}
};
salesOffices.listen(function (price, squareMeter) {
console.log("价格" + price + '平方' + squareMeter);
});
salesOffices.listen(function (price, squareMeter) {
console.log("价格" + price + '平方' + squareMeter);
})
salesOffices.trigger('3w', 88);
salesOffices.trigger('2w', 120);
复制代码
这里存在一个问题,如果小明只想订阅小户型却将所有的房价信息发送给了他,所以这里需要加一个标识key,让订阅者只订阅自己感兴趣的户型房价。
订阅指定key的发布订阅模式
var salesOffices = {
clientList: [],
listen(key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn)
},
trigger(key, ...args) {
var fns = this.clientList[key]; // 当前的事件队列为
// 如果没有订阅过该消息
if (!fns && fns.length === 0) {
return false;
}
// 调用当前fn
for (let i = 0, fn; fn = fns[i++];) {
fn.call(this, ...args);
}
}
}
salesOffices.listen('squareMeter88', (price) => {
console.log("价格" + price);
});
salesOffices.listen('squareMeter120', (price) => {
console.log("价格" + price);
});
salesOffices.trigger('squareMeter88', '2w');
salesOffices.trigger('squareMeter120', '3w');
复制代码
此时就实现了订阅者只发送自己感兴趣的事件了
通用的发布订阅的实现
var event = {
clientList: [],
listen: function (key, fn) {
// 如果用户没有订阅该事件,则为该事件分配一个事件队列用于存放消息列表
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn); // 订阅的消息添加进缓存列表
},
trigger: function (key,...args) {
var fns = this.clientList[key]; // 当前订阅事件的所有消息列表
if (!fns || fns.length === 0) { // 如果没有绑定对应的消息
return false;
}
for (var i = 0, fn; fn = fns[i++];) {
fn.call(this, ...args); // (2)
}
}
};
// 定义一个installEvent函数,这个函数可以给所有的对象都动态的安装发布-订阅模式
var installEvent = function(obj) {
for(let i in obj) {
event[i] = obj[i];
}
}
// 这样不仅售楼部可以使用该工具,技术部如果想使用也可以直接安装installEvent方法
var salesOffices = {};
installEvent(salesOffices);
salesOffices.listen('squareMeter88', function (price) {
console.log("价格" + price);
})
salesOffices.listen('squareMeter120', function (price) {
console.log("价格" + price);
})
salesOffices.trigger('squareMeter88', '3w');
salesOffices.trigger('squareMeter120', '2w');
复制代码
移除订阅的事件
var event = {
clientList: [],
listen: function (key, fn) {
// 如果用户没有订阅该事件,则为该事件分配一个事件队列用于存放消息列表
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn); // 订阅的消息添加进缓存列表
},
trigger: function (key,...args) {
var fns = this.clientList[key]; // 当前订阅事件的所有消息列表
if (!fns || fns.length === 0) { // 如果没有绑定对应的消息
return false;
}
for (var i = 0, fn; fn = fns[i++];) {
fn.call(this, ...args); // (2)
}
},
remove(key,fn) {
var fns = this.clientList[key];
// 如果key对应的事件没有被人订阅,则直接返回
if(!fns) {
return false;
},
// 如果没有回调函数则将该事件队列清空
if(!fn) {
fns && (fns.length == 0)
} else {
// 删除数组里的元素需要使用倒叙删除,不然会出现数组的飘移问题
for(let k = fns.length - 1; k >= 0; k--) {
var _fn = fns[k];
// 如果当前的回调函数与事件队列的相同的则移除该项
if(_fn = fn) {
fns.splice(k,1)
}
}
}
}
};
复制代码
Events模块简单实现
根据上面的发布订阅模式的基础简单的事件node的EventEmitter
function EventEmitter() {
this._event = {};
}
EventEmitter.prototype.on = function (eventNames, cb) {
if (!this._event) this._event = {};
if (this._event[eventNames] || (this._event[eventNames] = [])) {
this._event[eventNames].push(cb);
}
}
EventEmitter.prototype.emit = function (eventNames, ...args) {
if (this._event && this._event[eventNames]) {
this._event[eventNames].forEach(events => {
events.call(this,...args)
});
}
}
// off的本质是将this._events[eventName]重新赋值,赋值为filter过后的值
// 直接过滤,找到索引采用splice删除 .filter(cb => cb !== callback);
// 最后一定要记得es6模块的导入导出
EventEmitter.prototype.off = function (eventNames, callback) {
if (this._event && this._event[eventNames]) {
this._event[eventNames] = this._event[eventNames].filter((event) => {
return (event !== callback && event.my !== callback))
}
}
}
// once的本质是先执行on让后触发off
EventEmitter.prototype.once = function (eventNames, callback) {
// 需要等on触发完毕以后再触发off事件
// this.on(eventNames, callback);
// this.off(eventNames, callback)
let once = (...args) => {
callback(...args);
// 此处调用的是off函数,传入的实际上是eat,需要删除的是once
this.off(eventNames, once)
}
once.my = callback;
this.on(eventNames, once)//先绑定一个一次性事件,稍后触发时,再将事件清空
}
module.exports = EventEmitter;
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END






















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)