js 观察者模式(发布-订阅者模式 或 消息机制)

javascrpt 观察者模式,又称发布-订阅模式或者消息机制。如果看到这几个名称,说的都是一回事。

其实在 javascript 中经常看到这种模式,比如 监听点击事件

// 监听点击事件 
document.querySelector('#btn').addEventListener('click',function () {
        alert('You click this btn');
 })

什么是观察者模式?比如生活中就像是去报社订报纸,你喜欢读什么报就去报社去交钱订阅,当发布了新报纸的时候,报社会向所有订阅了报纸的每一个人发送一份,订阅者就可以接收到。

那 javascript 如何实现观察者模式呢?

从以下几方面考虑:

  • 一个调度对象( { key: value } ),key 对应名称,value 对应数组,数组里是订阅者函数
  • 一个订阅方法,添加进调度对象
  • 一个移除方法,移除调度对象中对应的方法
  • 一个只订阅一次的方法,执行之后立刻移除

代码实现如下:

class Event {

    constructor() {
        // 调度对象
        this.events = {}
    }

    /**
     * 注册事件
     * @param {string} name 事件名称
     * @param {function} fn 执行函数
     */
    on(name, fn) {
        if (!this.events[name]) {
            this.events[name] = []
        }
        this.events[name].push(fn)
    }

    /**
     * 执行事件
     * @param {string} name 事件名称
     * @param  {...any} args 执行参数
     */
    emit(name, ...args) {
        if (!this.events[name]) {
            return
        }
        this.events[name].forEach(fn => {
            fn.call(this, ...args)
        })
    }

    /**
     * 移除事件
     * @param {string} name 事件名称
     * @param {function} fn 函数名称
     */
    off(name, fn) {
        if (!this.events[name]) {
            return
        }
        // 如果未传函数名称
        if (!fn) {
            this.events[name] = null
            return
        }

        const index = this.events[name].indexOf(fn)
        this.events[name].splice(index, 1)
    }

    /**
     * 只监听执行一次
     * @param {string} name 事件名称
     * @param {function} fn 执行函数
     */
    once(name, fn) {
        // 构建执行一次就移除方法
        const only = (...args) => {
            fn.call(this, ...args)
            this.off(name, only)
        }
        // 开始监听
        this.on(name, only)
    }
}

测试代码:

let events = new Event()
events.on('fire', function (name1, name2) {
    console.log(name1, name2)
})
events.once('fire1', function (name1, name2) {
    console.log('once', name1, name2)
})

setTimeout(() => {
    events.emit('fire', '参数1', '参数2')
    events.emit('fire1', '参数1', '参数2')
    events.emit('fire', '参数1', '参数2')
}, 1000)
Leave a Reply

Your email address will not be published. Required fields are marked *