前言
之前有写过 发布订阅和观察者模式的区别 ,但实现过程很草率,今天 比较详细的写一个发布订阅的实现。因为,发布订阅这哥们儿存在感太强 。哪哪都有他,node里很多都继承了内置的发布订阅模块实现的,最早我写jq的时候也都是它的影子例如:trigger和on方法
作用
- 百度上比较统一的解释:一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知
- 我的理解是:给回调事件做解耦,一个对象做多个回调事件的缓存,等待统一subscribe(发布)
实现
eventHub 对象结构
const eventHub = {
events: {},
on: () => {
//事件订阅
},
subscribe: () => {
//事件发布
},
off: (eventName, callback) => {
//事件off,停止订阅
},
once: (eventName, callback) => {
//只订阅一次
},
};
复制代码
事件订阅on()
- events 对象不存在的时候容错处理
- eventName不存在,以eventName作key,callback作值的数组为value 写入events对象中
- eventName存在,找到eventName作key 的数组里push 当前callback
on: (eventName, callback) => {
//events 对象不存,则返回一个{} 作 this.events
if (!this.events) {
this.events = {};
}
if (this.events[eventName]) {
this.events[eventName].push(callback);
} else {
this.events[eventName] = [callback];
}
}
复制代码
事件发布subscribe()
- 找events对象中eventName对应的回调事件数组 遍历&依次执行
subscribe: (eventName, ...args) => {
this.events[eventName].forEach((fn) => fn(...args));
},
复制代码
- 测试下效果
const cry = () => {
console.log("哭唧唧");
};
eventHub.on("zoe失恋了", cry);
setTimeout(() => {
eventHub.subscribe("zoe失恋了");//哭唧唧
}, 1000);
复制代码
事件发布off()
- 作用 :停止对特定事件对应的特定回调事件的订阅
- 实现:利用数组的filter 对该事件进行过滤 返回新的回调事件数组
off: (eventName, callback) => {
if (this.events && this.events[eventName]) {
// 删除需要被off的回调函数
this.events[eventName] = this.events[eventName].filter(
(fn) => fn !== callback
);
}
},
复制代码
- 实现效果
const cry = () => {
console.log("哭唧唧");
};
eventHub.on("zoe失恋了", cry);
const fn = () => {
console.log("shopping");
};
eventHub.on("zoe失恋了", fn);
setTimeout(() => {
// eventHub.off("zoe失恋了", fn);
eventHub.subscribe("zoe失恋了");//哭唧唧
}, 1000);
复制代码
无off时候的效果:
有off时候的效果(fn被顺利off的):
事件发布once()
- 基于on 和off 只给调用一次后,给off
once: (eventName, callback) => {
//定义一个one 事件 做eventName的订阅回调事件
const one = () => {
//one里执行callback的时候后及时off 自己
callback();
eventHub.off(eventName, one);
};
eventHub.on(eventName, one);
},
复制代码
- 调用
const cry = () => {
console.log("哭唧唧");
};
eventHub.on("zoe失恋了", cry);
const fn = () => {
console.log("shopping 钱难赚 不要每次失恋都去买东西");
};
eventHub.once("zoe失恋了", fn);
setTimeout(() => {
//eventHub.off("zoe失恋了", fn);//潜在问题
eventHub.subscribe("zoe失恋了");
eventHub.subscribe("zoe失恋了");
}, 1000);
复制代码
- 效果:第一次 cry和fn里的都会被打印,第二次只会打印cry
- 问题:把off注释去掉 once监听的事件没有被off成功
- 解决
once方法里里将callback赋值给one的一个属性l上
one.l = callback; //用于 还没subscribe 前 就执行了off的处理
eventHub.on(eventName, one);
复制代码
off里filter的时候加一个
(fn) => fn !== callback && fn.l !== callback
复制代码
- 效果:off 成功了(完美)
完整代码
const eventHub = {
events: {},
on: (eventName, callback) => {
if (!this.events) {
this.events = {};
}
if (this.events[eventName]) {
this.events[eventName].push(callback);
} else {
this.events[eventName] = [callback];
}
},
subscribe: (eventName, ...args) => {
this.events[eventName].forEach((fn) => fn(...args));
},
off: (eventName, callback) => {
if (this.events && this.events[eventName]) {
// 删除需要被off的回调函数
this.events[eventName] = this.events[eventName].filter(
(fn) => fn !== callback && fn.l !== callback
);
}
},
once: (eventName, callback) => {
const one = () => {
callback();
eventHub.off(eventName, one);
};
one.l = callback; //用于 还没subscribe 前 就执行了off的处理
eventHub.on(eventName, one);
},
};
复制代码
class的实现发布订阅
- 对象形式的实现 主要是为了理解整个过程
- 但实际工作中使用不可能要放在全局变量上,所以存在不易复用的问题
- 看过node里EventEmitter 的实现 是用class 所以用class的方式再写一遍
- 代码实现
function EventHubs() {
this.events = {};
}
EventHubs.prototype.on = function (eventName, callback) {
if (!this.event) {
this.event = {};
}
if (this.event[eventName]) {
this.event[eventName].push(callback);
} else {
this.event[eventName] = [callback];
}
};
EventHubs.prototype.subscribe = function (eventName, ...args) {
this.events[eventName].forEach((fn) => fn(...args));
};
EventHubs.prototype.off = function (eventName, callback) {
if (this.events && this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(
(fn) => fn !== callback && fn.l !== callback
);
}
};
EventHubs.prototype.once = function (eventName, callback) {
const one = () => {
callback();
this.off(eventName, one);
};
one.l = callback;
this.on(eventName, one);
};
module.exports = EventHubs;
复制代码
最后如果觉得本文有帮助 记得点赞三连哦 十分感谢
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END