写在前面:本文为个人在日常工作和学习中的一些总结,便于后来查漏补缺,非权威性资料,请带着自己的思考^-^。

Vue的组件通讯场景

  1. 父子组件通讯,使用props or 回调函数
  2. 状态较多、复杂,项目比较繁杂, 使用vuex or mobx 等状态管理工具
  3. 非父子组件,使用Event Bus

Event Bus使用的简单回顾

在日常开发中有关Event Bus我们是怎么用的呢?

// Vue instance
export default new Vue()

// component A
...
import EventBus from 'Vue instance'

...
methods: {
    fn() {}
},
created() {
    EventBus.$on('test', fn)
}
...

component B
import EventBus from 'Vue instance'
...
methods: {
    handleSomething() {
        EventBus.$emit('test', /* some params */)
    }
}
...

下面循着这个使用形式,尝试去实现一下

基本功能

Event Bus的基本功能无非是发布订阅模式的一种应用

class Event {
    constructor() {
        this._events = new Map();
    }
    $on(type, fn) {
        let handlers = [fn]
        if (this._events.has(type)) {
           handlers = this._events.get(type).concat(handlers)
        }
        this._events.set(type, handlers)
    }
    $emit(type, ...args) {
        const fns = this._events.get(type)
        if (!fns) return
        for(let i = 0; i < fns.length; i++) {
            fns[i].apply(this, args)
        }
    }
}

一个简单的发布/订阅类,测试一下

function testFn(a) {
    console.log(a)
}

const emitter = new Event()
emitter.$on('test', testFn) // 订阅'test'事件
emitter.$emit('test', 'cont') // 发布'test'事件

结果打印 'cont', 表明已经正常工作

多个监听者呢?

function testFn1(a) {
    console.log('1' + a)
}

function testFn2(a) {
    console.log('2' + a)
}

emitter.$on('test', testFn1)
emitter.$on('test1', testFn2)
emitter.$emit('test', '1')
emitter.$emit('test1', '2')

结果也可以正常运行

至此,表明该事件类可以支持多个事件监听,也可以支持一个事件多个监听者函数

移除监听者函数

接下来更新一下需求: Event类允许移除之前添加的某一个监听者

class Event{
    constructor() {
        ...
    }
    
    ...
    
    $off(type, fn) {
        if (this._events.has(type)) {
            if (!fn) { // 如果没有指定监听者函数,我们认为想要移除所有的type事件的监听者函数
                this._events.delete(type)
            } else { // 移除事件的fn监听者函数
                const handlers = this._events.get(type)
                const position = handlers.findIndex(_ => _ === fn)
                handlers.splice(position, 1)
                this._events.set(type, handlers)
            }
        }
    }
}

之前我们在'test'事件中绑定了2个监听者函数testFn 和 testFn1,现在试着解除一个testFn1

emitter.$off('test', testFn1)
emitter.$emit('test', 'z')

发现现在就只执行了testFn这个监听者函数
注: 匿名函数作为监听者函数是不能被移除的

只监听一次的情况

某些场景,我们希望对某个事件的触发只做出一次响应,此时可以通过包装监听者函数,当监听者函数在执行后便进行$off操作,解绑事件

...
$once(type, fn) {
    const me = this
    function on(...args) {
        me.$off(type, on)
        fn.apply(me, args)
    }
    this.$on(type, on)
}
...

一点问题

在现有代码的基础上如果这样会怎么样呢?

function fn(a) {
    console.log(a)
}
emitter.$on('ev', fn)
emitter.$on('ev', fn)

结果是fn函数会被执行2次,也就是说现在同一个事件,相同的监听者函数可以重复进行添加
这是不是问题呢? 视场景而定吧。。。

最终完整代码

class Event {
    constructor() {
        this._events = new Map();
    }
    $on(type, fn) {
        let handlers = [fn]
        if (this._events.has(type)) {
           handlers = this._events.get(type).concat(handlers)
        }
        this._events.set(type, handlers)
    }
    $off(type, fn) {
        if (this._events.has(type)) {
            if (!fn) { // 如果没有指定监听者函数,我们认为想要移除所有的type事件的监听者函数
                this._events.delete(type)
            } else { // 移除事件的fn监听者函数
                const handlers = this._events.get(type)
                const position = handlers.findIndex(_ => _ === fn)
                handlers.splice(position, 1)
                this._events.set(type, handlers)
            }
        }
    }
    $once(type, fn) {
        const me = this
        function on(...args) {
            me.$off(type, on)
            fn.apply(me, args)
        }
        this.$on(type, on)
    }
    $emit(type, ...args) {
        const fns = this._events.get(type)
        if (!fns) return
        for(let i = 0; i < fns.length; i++) {
            fns[i].apply(this, args)
        }
    }
}

总结

其实Vue中的Event Bus就是发布/订阅模式的一个应用

THE END


innocence
11 声望1 粉丝

undefined