(不背八股系列)发布订阅模式

279 阅读3分钟

前言

看了很多发布订阅模式的文章,每次看完都脑子发懵,为什么看完不过脑啊!我觉得大概率的原因是那些文章举的例子都离我太远了,以至于我都不知道我什么时候会用到。
这个系列可是 《不背八股系列》,要的就是你看完之后不再是去背诵这个知识点,更多的是去理解并能真正应用这个知识点。话不多说,让我们赶紧进入正题吧~

正题

“发布订阅” 听起来就挺抽象的,但是如果是熟悉用vue的小伙伴,对$on$emit应该不会觉得陌生吧。没错,他们俩就是一个东西,一个是概念,一个是实际的应用,$on$emit就是“发布订阅”模式的具体实现。下面我就举几个大家平时开发中总能用到的例子,来带大家看看这个 “抽象” 的发布订阅到底是个啥,有什么用?

  • Vue开发中的应用

    • 父子间通信:当父组件需要用到子组件的数据时,要怎么实现子组件数据向父组件传递呢?日常开发中我们一般采用的就是自定义事件,那我们就会使用v-on:自定义事件 = "cb"这样的形式来绑定事件(到这里如果大家会想:“这里绑定事件是绑定到谁身上呢”,那就证明大家真的在认真思考!)这里我查看了vue的相关源码,发现只要是v-xxx指令,其实都是绑定在我们使用的VNode身上,这也就是我们平时在<input>标签使用@input = (e)=>e.target能获取到这个target的原因,所以这里的自定义事件其实是绑定到了子组件的vm实例身上,那么接下来绑定到了vm实例身上的事件通过this.$emit(自定义事件,参数)来通知触发,就像下图。

    image.png

    注意: 这里面其实缺省了一个很重要的步骤,就是到底是谁来触发这个回调的,大家平时开发都会默认的想:“子组件emit,那父组件同名的事件就会触发回调,就可以拿到数据啦~”
    关键点: 弄清楚是谁调用这个回调,那么我们的正题 “发布订阅”模式 你就明白了。

做一个试试?

前面说到子组件和父组件之间通信缺少一个很重要的步骤,这个步骤涉及到两个问题:

  1. 父组件中的callback是怎么触发的?
  2. 子组件的数据又是怎么让父组件的callback使用到的呢?

仔细看上面的两个问题,我们可以想到一个方法,那就是把父组件的callback给存到子组件中,让子组件传入子组件的数据作为父组件的callback的参数来调用,从而实现父组件的callback使用到子组件的数据,那么我们这个步骤就填上了!没错,Vue其实就是这么做的,这也就是发布订阅模式了!
还不理解的小伙伴不用急,我们还有代码实操(代码胜千言!),来实现一下超级简易的发布订阅模式的代码

class EventCenter {
    constructor() {
        this.eventCt = {}
    }

    /**
     * @param {String} event 订阅事件
     * @param {Function} cb 事件触发时执行的回调
     */
    $on(event, cb) {
        // 事件中心没有存该事件,就给他存个事件key和value为[]进去 , 将回调push进去对应的事件中
        (this.eventCt[event] || (this.eventCt[event] = [])).push(cb)
        // 上面这个写法我超爱!是vue源码中的写法
    }

    /**
     * @param {String} event 触发的事件
     * @param  {...any} args 传递的参数
     */
    $emit(event, ...args) {
        // 有注册了这个事件,那就取出来在这执行了
        if (this.eventCt[event].length) {
            const cbs = this.eventCt[event]
            cbs.forEach(cb => {
                cb(...args)
            });
        }
    }
}
const vm = new EventCenter()
// 这里可以类比父组件在子组件标签绑定 自定义事件 和 回调
vm.$on('ZiDingYi', (clickName, clickTime) => {
    console.log(clickName, '在时间点:', clickTime, '触发的');
})

// 这里类比子组件使用this.$emit的过程
vm.$emit('ZiDingYi', 'charry', Date.now())

存在疑惑

到这里你应该就理解了吧!其实就是子组件的vm身上存了这个callback才能把子组件数据传到这个保存的callback来实现子组件数据传给父组件回调。

写在最后: 不过我在这里有个疑问,就是每个事件为什么要用一个数组来存这个回调呢?如果一个事件对应了多个回调,那么当触发事件时其他回调都会执行,如果还要传参岂不是每个回调都会重复触发,什么样的需求会使用到呢?有明白的小伙伴可以在评论区给我指导指导,谢谢大家的阅读~