发布订阅模式还不会??戳这里,50行核心代码,手把手教你学会

小插曲

事件

  • 建议你们看下官网中events事件的描述node中events事件
  • 发布订阅模式定义了一种一对多的依赖关系
  • 在Node中EventEmitter开放on(事件名,回调函数)用于订阅事件
  • emit(事件名)用于发布事件,可能对应多个订阅事件,让订阅事件依次执行

不明白?不要紧。举个最简单例子,女人失恋了会哭,还会找新男友,在这里哭和找男友至关于订阅女人失恋的回调,何时执行呢?当发布女人失恋这件事的时候,说的这么抽象,直接来一段代码吧html

  • 是否是很简单,只有发布这个事件时候,被订阅的事件才会依次执行,造成一对多的依赖关系。接下来直接写源码实现

思路构建

  • 咱们想构造一个相似这样的对象 {"失恋":[cry,findBoy]},当事件发布的时候,让数组中对应的函数依次执行,就实现了这样的效果
  • 先讲个小知识点 {}和Object.create(null)区别。 {}有做用链,经过Object.create(null)创造的空对象没有做用链,给你们演示下,其它就没啥区别。源码就是这样写(逼格高)

实现events模块

一、on和emit 两个核心方法

  • 源码实现
// 声明EventEmitter事件发生器构造函数
function EventEmitter() {
    this._events = Object.create(null);
}
//on 订阅方法实现  由于在实例上调用,因此写在原型上
EventEmitter.prototype.on = function(type,callback){
    // 若是实例不存在则建立一个空对象,Object.create(null)没有链
    if(!this._events) {
        this._events = Object.create(null);
    }
    if(this._events[type]){ //若是失恋有对应的值,直接往数组push
        this._events[type].push(callback)
    }else { //第一次订阅,没有失恋,就声明{失恋:[cry]}
        this._events[type] = [callback];
    }
};
// emit方法实现
EventEmitter.prototype.emit = function(type){
    if(this._events[type]){ //{失恋:[cry,eat]} 若是失恋对应有值,依次执行里面的方法
        this._events[type].forEach(fn=>fn())
    }
};
module.exports = EventEmitter
复制代码
  • 十几行代码就实现核心功能,这么简单?对 就是这么简单,赶快来测试下吧

二、removeListener 取消订阅事件,失恋了不想哭了,因此咱们提供个移除监听的方法

  • 比较简单,直接上代码吧看的直接
// 移除订阅事件的方法
EventEmitter.prototype.removeListener = function(type,callback){
    if(this._events[type]){
        // 返回false就表示不要了,用filter实现去重
        this._events[type] = this._events[type].filter(fn=>fn!==callback)
    }
};
复制代码
  • 测试下吧,失恋了不想哭了
  • 完美实现,是否是很激动。

三、removeAllListeners移除所有的监听器,与removeListener相对应

// removeAllListeners 移除全部的监听者
EventEmitter.prototype.removeAllListeners = function(){
//简单粗暴,直接赋值空对象 {}
    this._events = Object.create(null);
};
复制代码
  • 测试下,失恋了既不想哭,也不想找对象,什么也不打印就对拉

四、扩展once方法 咱们但愿哭的事件 屡次发布emit时候只执行一次,也就表明执行一次后须要将事件从对应关系中移除掉。

// once实现
EventEmitter.prototype.once = function(type,callback,flag){
    // 先绑定 调用后再删除,运用了one函数 {失恋:one}
    let one = (...args)=> {
        callback(...args);
        this.removeListener(type, one);
    }
    //自定义属性 由于实例中没有one属性
    one.l = callback;
    this.on(type,one)
};
// 移除订阅事件的方法
EventEmitter.prototype.removeListener = function(type,callback){
    if(this._events[type]){
        // 返回false就表示不要了,用filter实现去重
        this._events[type] = this._events[type].filter(fn=>fn!==callback && fn.l!==callback)
    }
};
复制代码
  • 你可能会疑惑为何声明一个wrap函数,设想下,否则你告诉我怎么先绑定一次,在移除。不少人可能都会这么写
  • 错误例子 错误例子 错误例子(重要事情说三遍)
// - 错误例子 错误例子 错误例子(重要事情说三遍)
//你可能会这么写,但刚绑定就移除拉,体会这意思了吧
EventEmitter.prototype.once = function(type,callback){
//先绑定在移除
    this.on(type,callback);
    this.removeListener(type,callback)
};
复制代码
  • 测试下吧,一步一个脚印

五、newListener方法。当cry添加到内部监听数组({失恋:[cry]})以前,会触发自身的'newListener'事件

  • 没听懂?咱们先来看官方的用法

简单说就是能够监控到订阅的事件类型,上源码看下如何实现node

//on 订阅方法实现  由于在实例上调用,因此写在原型上
EventEmitter.prototype.on = function(type,callback){
    // 若是实例不存在则建立一个空对象,Object.create(null)没有链
    if(!this._events) {
        this._events = Object.create(null);
    }
    if(type!=="newListener"){
        if(this._events["newListener"]){
            this._events["newListener"].forEach(fn=>fn(type))
        }
    }
    if(this._events[type]){ //若是失恋有对应的值,直接往数组push
        this._events[type].push(callback)
    }else { //第一次订阅,没有失恋,就声明{失恋:[cry]}
        this._events[type] = [callback];
    }
};
复制代码
  • 测试下吧

看到这里,基本方法都实现了。不经常使用就不解释拉。 若是你们想看全部源码方法解析,能够点进我github上参考

相关文章
相关标签/搜索