eventBus的4个基本事件
on
发布和订阅
发布和订阅。在type上添加订阅者
emit
执行该订阅下的所有函数
执行该订阅下的所有函数。遍历type下的订阅者,执行。
off
取消某个函数的订阅
取消某个函数的订阅。在订阅者中找到fn然后删除
once
只执行一次订阅事件
once方法将handler函数挂载了type这个发布者上,如果执行emit就会执行handler函数中的内容,会先删除type上的所有的函数,然后执行fn。
// 组件通信,一个触发与监听的过程
class EventBus {
constructor() {
this._events = new Map();//存储发布者和订阅者
}
on(type, fn) {//发布和订阅
//获取到该发布者下的订阅者集合handler
let handler = this._events.get(type);
//如果还没有订阅者
if (!handler) {
this._events.set(type, [fn]);//加入这个订阅者
} else {
handler.push(fn);//直接push进去handler
}
}
emit(type, ...args) {//执行该订阅下的所有函数
let handler = this._events.get(type);
for (let i = 0; i < handler.length; i++) {
handler[i].apply(this, args);//执行第i个订阅者函数
}
}
off(type, fn) {//取消某个函数的订阅
let handler = this._events.get(type);//获取所有的订阅者
//数组中订阅者有多个,找到等于fn的订阅者删除
handler.splice(handler.findIndex(e => e === fn), 1);
}
//只执行一次
once(type, fn) {
let _self = this;//拿到当前实例对象的this
function handler() {
_self.off(type, handler);//删除type下的handler函数
fn.apply(null, arguments);//执行该函数
}
this.on(type, handler)//type下添加handler函数
}
}
工作原理在于对事件的监听与手动触发:
作为一个事件总线,它应当具备一个任务队列,以及三个方法:订阅方法、发布方法、取消订阅
class EventBus {
constructor() {
this.tasks = {}; // 按事件名称创建任务队列
}
/**
* 注册事件(订阅)
* @param {String} type 事件名称
* @param {Function} fn 回调函数
*/
on(type, fn) {
// 如果还没有注册过该事件,则创建对应事件的队列
if (!this.tasks[type]) {
this.tasks[type] = [];
}
// 将回调函数加入队列
this.tasks[type].push(fn);
}
/**
* 注册一个只能执行一次的事件
* @params type[String] 事件类型
* @params fn[Function] 回调函数
*/
once(type, fn) {
if (!this.tasks[type]) {
this.tasks[type] = [];
}
const that = this;
// 注意该函数必须是具名函数,因为需要删除,但该名称只在函数内部有效
function _once(...args) {
fn(...args);
that.off(type, _once); // 执行一次后注销
}
this.tasks[type].push(_once);
}
/**
* 触发事件(发布)
* @param {String} type 事件名称
* @param {...any} args 传入的参数,不限个数
*/
emit(type, ...args) {
// 如果该事件没有被注册,则返回
if (!this.tasks[type]) {
return;
}
// 遍历执行对应的回调数组,并传入参数
this.tasks[type].forEach((fn) => fn(...args));
}
/**
* 移除指定回调(取消订阅)
* @param {String} type 事件名称
* @param {Function} fn 回调函数
*/
off(type, fn) {
const tasks = this.tasks[type];
// 校验事件队列是否存在
if (!Array.isArray(tasks)) {
return;
}
// 利用 filter 删除队列中的指定函数
this.tasks[type] = tasks.filter((cb) => fn !== cb);
}
}