发布订阅的实现

前言

之前有写过 发布订阅和观察者模式的区别 ,但实现过程很草率,今天 比较详细的写一个发布订阅的实现。因为,发布订阅这哥们儿存在感太强 。哪哪都有他,node里很多都继承了内置的发布订阅模块实现的,最早我写jq的时候也都是它的影子例如:trigger和on方法

作用

  • 百度上比较统一的解释:一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知
  • 我的理解是:给回调事件做解耦,一个对象做多个回调事件的缓存,等待统一subscribe(发布)

实现

eventHub 对象结构

const eventHub = {
  events: {},
  on: () => { 
  //事件订阅
  },
  subscribe: () => {
    //事件发布
  },
  off: (eventName, callback) => {
   //事件off,停止订阅
  },
  once: (eventName, callback) => {
   //只订阅一次
  },
};
复制代码

事件订阅on()

  • events 对象不存在的时候容错处理
  • eventName不存在,以eventName作key,callback作值的数组为value 写入events对象中
  • eventName存在,找到eventName作key 的数组里push 当前callback
  on: (eventName, callback) => {
  //events 对象不存,则返回一个{} 作 this.events
    if (!this.events) {
      this.events = {};
    }
    if (this.events[eventName]) {
      this.events[eventName].push(callback);
    } else {
      this.events[eventName] = [callback];
    }
  }
复制代码

事件发布subscribe()

  • 找events对象中eventName对应的回调事件数组 遍历&依次执行
 subscribe: (eventName, ...args) => {
    this.events[eventName].forEach((fn) => fn(...args));
  },
复制代码
  • 测试下效果
const cry = () => {
  console.log("哭唧唧");
};
eventHub.on("zoe失恋了", cry);
setTimeout(() => {
  eventHub.subscribe("zoe失恋了");//哭唧唧
}, 1000);
复制代码

image.png

事件发布off()

  • 作用 :停止对特定事件对应的特定回调事件的订阅
  • 实现:利用数组的filter 对该事件进行过滤 返回新的回调事件数组
off: (eventName, callback) => {
    if (this.events && this.events[eventName]) {
      // 删除需要被off的回调函数
      this.events[eventName] = this.events[eventName].filter(
        (fn) => fn !== callback 
      );
    }
  },
复制代码
  • 实现效果
const cry = () => {
  console.log("哭唧唧");
};
eventHub.on("zoe失恋了", cry);
const fn = () => {
  console.log("shopping");
};
eventHub.on("zoe失恋了", fn);
setTimeout(() => {
    // eventHub.off("zoe失恋了", fn);
  eventHub.subscribe("zoe失恋了");//哭唧唧
}, 1000);
复制代码

无off时候的效果:
image.png
有off时候的效果(fn被顺利off的):
image.png

事件发布once()

  • 基于on 和off 只给调用一次后,给off
 once: (eventName, callback) => {
 //定义一个one 事件 做eventName的订阅回调事件
    const one = () => {
    //one里执行callback的时候后及时off 自己
      callback();
      eventHub.off(eventName, one);
    };
    eventHub.on(eventName, one);
  },
复制代码
  • 调用
const cry = () => {
  console.log("哭唧唧");
};
eventHub.on("zoe失恋了", cry);
const fn = () => {
  console.log("shopping 钱难赚 不要每次失恋都去买东西");
};
eventHub.once("zoe失恋了", fn);
setTimeout(() => {
//eventHub.off("zoe失恋了", fn);//潜在问题
  eventHub.subscribe("zoe失恋了");
  eventHub.subscribe("zoe失恋了");
}, 1000);

复制代码
  • 效果:第一次 cry和fn里的都会被打印,第二次只会打印cry

image.png

  • 问题:把off注释去掉 once监听的事件没有被off成功

image.png

  • 解决

once方法里里将callback赋值给one的一个属性l上

  one.l = callback; //用于 还没subscribe 前 就执行了off的处理
  eventHub.on(eventName, one);
复制代码

off里filter的时候加一个

   (fn) => fn !== callback  && fn.l !== callback
复制代码
  • 效果:off 成功了(完美)

image.png

完整代码

const eventHub = {
  events: {},
  on: (eventName, callback) => {
    if (!this.events) {
      this.events = {};
    }
    if (this.events[eventName]) {
      this.events[eventName].push(callback);
    } else {
      this.events[eventName] = [callback];
    }
  },
  subscribe: (eventName, ...args) => {
    this.events[eventName].forEach((fn) => fn(...args));
  },
  off: (eventName, callback) => {
    if (this.events && this.events[eventName]) {
      // 删除需要被off的回调函数
      this.events[eventName] = this.events[eventName].filter(
        (fn) => fn !== callback  && fn.l !== callback
      );
    }
  },
  once: (eventName, callback) => {
    const one = () => {
      callback();
      eventHub.off(eventName, one);
    };
    one.l = callback; //用于 还没subscribe 前 就执行了off的处理
    eventHub.on(eventName, one);
  },
};
复制代码

class的实现发布订阅

  • 对象形式的实现 主要是为了理解整个过程
  • 但实际工作中使用不可能要放在全局变量上,所以存在不易复用的问题
  • 看过node里EventEmitter 的实现 是用class 所以用class的方式再写一遍
  • 代码实现
function EventHubs() {
  this.events = {};
}
EventHubs.prototype.on = function (eventName, callback) {
  if (!this.event) {
    this.event = {};
  }
  if (this.event[eventName]) {
    this.event[eventName].push(callback);
  } else {
    this.event[eventName] = [callback];
  }
};
EventHubs.prototype.subscribe = function (eventName, ...args) {
  this.events[eventName].forEach((fn) => fn(...args));
};
EventHubs.prototype.off = function (eventName, callback) {
  if (this.events && this.events[eventName]) {
    this.events[eventName] = this.events[eventName].filter(
      (fn) => fn !== callback && fn.l !== callback
    );
  }
};

EventHubs.prototype.once = function (eventName, callback) {
  const one = () => {
    callback();
    this.off(eventName, one);
  };
  one.l = callback;
  this.on(eventName, one);
};
module.exports = EventHubs;
复制代码

最后如果觉得本文有帮助 记得点赞三连哦 十分感谢

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享