神奇的EventBus

116 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第22天,点击查看活动详情

前言

在写文章之前先粘贴一段代码:

import vue from 'vue'
export const eventBus = new vue();

是不是很熟悉,有多少人在项目中使用过它? 实际上这个就是我们要讲的EventBus,又叫事件总线。那它解决了什么呢?

组件之间有父子组件通信和兄弟组件通信,由于父子组件之间有相互联系,所以一般都是用props来解决。而兄弟之间没有联系,所以一般都是用Vuex等全局共享状态来解决的。除此之外,还可使用事件总线来达到我们的目的。总结一句话就是只要两者之间没有任何关系就可使用事件总线来达到通信的目的。

什么是事件总线

事件总线其原理就在于使用发布订阅的方式,可以大大简化组件或者模块之间的通信。组件或者模块可以手动将事件保存到EventBus中,只要其它组件或者模块订阅了这个事件,事件总线就会将数据信息发送给其它组件或者模块。

一个简单的例子:打车:

包含三种角色和两种事件:分别是顾客,打车平台,司机,顾客发布打车信息,司机接受顾客打车。这里是不是EventBus极其类似。

实现EventBus

要想实现,首先得搞清楚它具有什么功能。

  • $on(name,callback):事件的监听器,可以同名
  • $emit(name,...arg):负责将触发的事件送到EventBus,还可以带参数,毕竟是要进行通信的嘛
  • $off([name,callback]):移除掉EventBus中具体的事件或者全部事件
  • $once(name,callback):监听的事件只执行一次,执行完之后将其删除
// import vue from 'vue'
// export const eventBus = new vue();
class EventBus {
    constructor() {
        this.init();
    }
    init() {
        this.EventBusObj = {};
    }
    /**
     * 
     * @param {string} name :订阅事件的名称
     * @param {function} callback:订阅具体的事件函数 
     */
    $on(name, callback, isOnce = false) {
        if (!this.EventBusObj[name]) {
            this.EventBusObj[name] = []
        }
        this.EventBusObj[name].push({ callback, isOnce })
    }
    /**
     * 
     * @param {string} name : 发布事件的名称
     * @param  {...any} args :发布时携带的参数
     */
    $emit(name, ...args) {
        if (!this.EventBusObj[name]) return; // 说明该事件并没有被注册
       this.EventBusObj[name] = this.EventBusObj[name].filter(fn => {
            const { callback, isOnce } = fn;
            callback(...args);
            if (!isOnce) {
                // 说明只能执行一次
                return {
                    callback,
                    isOnce:false
                }
            }
        });
    }
    /**
     * 
     * @param {String} name:需要取消订阅事件的名称 
     * @param {*} callback :取消多个中的一个
     */
    $off(name, callback) {
        if (!name && !callback) {
            // 这种就是什么都不传,直接情况
            this.EventBusObj = {}
        }
        if (!name) return;//预防不传name
        if (name && !callback) {
            // 给了name,但是没有指定的函数
            this.EventBusObj[name] = [];
        }
        if (name && callback) {
            // 给了取消的name和函数
            const eventArr = this.EventBusObj[name];
            this.EventBusObj[name] = eventArr.filter(fn => fn.callback !== callback);

        }
    }
    /**
     * 
     * @param {String} name:执行一次的name 
     * @param {*} callback :执行一次的函数
     */
    $once(name, callback) {
        //    重复使用on和off
        this.$on(name, callback, true)
    }
}

先实例化一个EventBus,再订阅几个事件:


//初始化实例
const e = new EventBus();

// 订阅事件
e.$on('e1',(arg)=>{
    console.log("事件1的第一个事件",arg)
})
e.$on("e1",(arg)=>{
    console.log('事件1的第二个事件',arg)
})

测试发布事件

e.$emit('e1','测试发布事件')

结果:

测试只执行一次:

//订阅事件
e.$once('e2', (arg) => {
    console.log("事件2的第一个事件,只执行一次", arg)
})
e.$once('e2', (arg) => {
    console.log("事件2的第二个事件,只执行一次", arg)
})

//发布事件
e.$emit('e2', '测试执行一次的订阅事件第一次执行')
e.$emit('e2', '测试执行一次的订阅事件的第二次执行');
console.log("执行完成")

结果:

测试取消事件:

function fn2(arg){
    console.log('事件2的第一个事件', arg)
}
// 订阅事件
e.$on('e1', (arg) => {
    console.log("事件1的第一个事件", arg)
})
e.$on("e2", fn2)
e.$on("e3", (arg) => {
    console.log('事件3的第一个事件', arg)
})
e.$on("e4", (arg) => {
    console.log('事件4的第一个事件', arg)
})
// 测试只传name
e.$emit('e1','测试只传name第一次')
e.$off('e1');
e.$emit('e1','测试只传name第二次')

// 测试传了name和事件
e.$emit('e2',"测试都传的第一次")
e.$off('e2',fn2)
e.$emit('e2',"测试都传的第二次")

// 都不传
e.$emit('e3','e3都不传的第一次')
e.$emit('e4','e4都不传的第一次')
e.$off();
e.$emit('e3','e3都不传的第二次')
e.$emit('e4','e4都不传的第二次')
//测试执行一次的订阅事件
console.log("执行完成")

结果:

最后来看其实是有一点小问题的: 比如实例化两个怎么办,要么在EventBus.js直接导出实例预防,要么就是在实力化时做一层防护:

    ......
static instanceObj;//静态属性
    constructor() {
        this.init();
    }
    init() {
        if(!EventBus.instanceObj){
            EventBus.instanceObj = {};
            this.EventBusObj = EventBus.instanceObj;
            return 
        }
        this.EventBusObj = EventBus.instanceObj;
    }
    .....

这样便可以预防实例化多个情况的发生。

end

虽然这种方式很神奇,但是不建议在项目中使用,后期很难维护,尤其后期改的时候不是自己写的代码特别难受。前车之鉴。