定义

发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。
订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

实现思路

  1. 创建一个 EventEmitter 类
  2. 在该类上创建一个事件中心(Map)
  3. on 方法用来把函数 fn 都加到事件中心中(订阅者注册事件到调度中心)
  4. emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应事件中心中的函数(发布者发布事件到调度中心,调度中心处理代码)
  5. off 方法可以根据 event 值取消订阅(取消订阅)
  6. once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)
  7. 注册一个 newListener 用于监听新的事件订阅

第一步,创建一个类,并初始化一个事件存储中心

class EventEmitter{
    // 用来存放注册的事件与回调
    constructor(){
        this._events = {};
    }
}

第二步,实现事件的订阅方法 on

基本思路:将事件回调函数存储到对应的事件上

class EventEmitter{
    // 用来存放注册的事件与回调
    constructor(){
        this._events = {};
    }

    on(eventName, callback){
        // 由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
        const callbacks = this._events[eventName] || [];
        callbacks.push(callback);
        this._events[eventName] = callbacks
    }
}

第三步,实现事件的发布方法 emit

基本思路:获取到事件对应的回调函数依次执行

class EventEmitter{
    // 用来存放注册的事件与回调
    constructor(){
        this._events = {};
    }

    // args 用于收集发布事件时传递的参数
    emit(eventName, ...args){
        const callbacks = this._events[eventName] || [];
        callbacks.forEach(cb => cb(...args))
    }
}

第四步,实现事件的取消订阅方法 off

基本思路:找到事件对应的回调函数,删除对应的回调函数

class EventEmitter{
    // 用来存放注册的事件与回调
    constructor(){
        this._events = {};
    }
    
    
    off(eventName, callback){
        const callbacks = this._events[eventName] || []

        const newCallbacks = callbacks.filter(fn => fn != callback && fn.initialCallback != callback /* 用于once的取消订阅 */)

        this._events[eventName] = newCallbacks;
    }
}

第五步,实现事件的单次订阅方法 once

基本思路: 1.先注册 2.事件执行后取消订阅

class EventEmitter{
    // 用来存放注册的事件与回调
    constructor(){
        this._events = {};
    }
    
    // 
    once(eventName, callback){
        // 由于需要在回调函数执行后,取消订阅当前事件,所以需要对传入的回调函数做一层包装,然后绑定包装后的函数
        const one = (...args)=>{
            // 执行回调函数
            callback(...args)
            // 取消订阅当前事件
            this.off(eventName, one)
        }
        // 考虑:如果当前事件在未执行,被用户取消订阅,能否取消?



        // 由于:我们订阅事件的时候,修改了原回调函数的引用,所以,用户触发 off 的时候不能找到对应的回调函数
        // 所以,我们需要在当前函数与用户传入的回调函数做一个绑定,我们通过自定义属性来实现
        one.initialCallback = callback;
        this.on(eventName, one)
    }
}

第六步,注册一个 newListener 用于监听新的事件订阅

基本思路:在用户注册的事件的时候,发布一下newListener事件

class EventEmitter{
    // 用来存放注册的事件与回调
    constructor(){
        this._events = {};
    }

    on(eventName, callback){

        // 如果绑定的事件不是newListener 就触发改回调
        if(this._events[eventName]){
            if(this.eventName !== "newListener"){
                this.emit("newListener", eventName)
            }
        }
        // 由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
        const callbacks = this._events[eventName] || [];
        callbacks.push(callback);
        this._events[eventName] = callbacks
    }
}

测试用例


const events = new EventEmitter()

events.on("newListener", function(eventName){
    console.log(`eventName`, eventName)
})

events.on("hello", function(){
    console.log("hello");
})

let cb = function(){
    console.log('cb');
}
events.on("hello", cb)

events.off("hello", cb)

function once(){
    console.log("once");
}
events.once("hello", once)

events.off("hello", once)
events.emit("hello")
events.emit("hello")

完整的代码

class EventEmitter{
    constructor(){
        this._events = {};
    }

    on(eventName, callback){
        if(this._events[eventName]){
            if(this.eventName !== "newListener"){
                this.emit("newListener", eventName)
            }
        }
        const callbacks = this._events[eventName] || [];
        callbacks.push(callback);
        this._events[eventName] = callbacks
    }

    emit(eventName, ...args){
        const callbacks = this._events[eventName] || [];
        callbacks.forEach(cb => cb(...args))
    }

    once(eventName, callback){
        const one = (...args)=>{
            callback(...args)
            this.off(eventName, one)
        }
        one.initialCallback = callback;
        this.on(eventName, one)
    }

     off(eventName, callback){
        const callbacks = this._events[eventName] || []
        const newCallbacks = callbacks.filter(fn => fn != callback && fn.initialCallback != callback /* 用于once的取消订阅 */)
        this._events[eventName] = newCallbacks;
    }
}