什么是发布/订阅者模式?

相信大家在看Vue的视图更新以及$emit事件源码都有见到发布订阅的身影。包括Nodejs Events、jQuery中同样都有实现发布订阅模式,用网上一张比较形象的图:

image.png

左边是观察者模式,右边是发布订阅模式:定义有事件名的若干个订阅者集合进行维护(增删改查),在发布(emit)的时候通过唯一的事件名(key)把对应的订阅者集合中的方法逐个依次执行一遍。

具体实现

  var eventEmitter = {
    list: {},
    //订阅主题
    on: function (event, fn) {
      if (typeof fn !== "function") {
        return false;
      }
      //创建订阅者列表,如果存在就直接插入
      (this.list[event] || (this.list[event] = [])).push(fn);
      return this;
    },
    //发布主题
    emit: function () {
      var event = [].shift.call(arguments);
      if (this.list[event] && this.list[event].length) {
        var fns = this.list[event].slice();
        //浅拷贝后直接对列表所有订阅者函数依次执行
        for (var i in fns) {
          this.list[event][i].apply(this, arguments);
        }
        return this;
      }
      return false;
    },
}

onemit的话比较简单
首先定义一个list对象用于存放事件的集合的映射表
当调用on事件绑定的时候通过传入的事件名判断当前是否已存在list中,不存在则先设置一个空数组,否则就直接push进去。

emit发布执行对应事件event对入参arguments进行处理(shift剪出要触发的事件名),通过事件名先浅拷贝一个列表副本,然后遍历执行对应列表的所有的函数this.list[event][i].apply(this, arguments)

    //移除对应订阅者
    remove: function (event, fn) {
      var fns = this.list[event];
      if (!fns) return false;
      //如没传递对应的订阅者函数引用,就默认删除整个事件列表
      if (!fn) {
        delete this.list[event];
        return this;
      }
      //找到对应的订阅者进行删除,包括once的订阅者
      for (var i = 0; i <= fns.length; i++) {
        if (fns[i] === fn || fns.fn === fn) {
          fns.splice(i, 1);
          break;
        }
      }
      return this;
    },
    //创建执行后立即销毁的订阅者
    once(event, fn) {
      function once() {
        this.remove(event, once);
        fn.apply(this, arguments);
      }
      //存储当前fn副本用于删除时的查找
      once.fn = fn;
      this.on(event, once);
      return this;
    },

  };

remove删除事件先获取fns对应主题的函数列表进行一些判断,如果没指定删除列表中的哪个函数(函数引用)就默认把对应整个列表给删除,如果有传fn就在循环中和对应的函数进行引用的判断fns.fn === fn是给once函数删除的时候使用的

once这里给传入的订阅者包装成一个闭包函数,把订阅者fn放在订阅者once函数属性下,当对应订阅者执行的时候先执行这个闭包函数删除掉自身后再去执行挂在once下的订阅者fn,做到用完即删。

因为如果想使用remove方法删除once订阅者的话和删除普通订阅者不一样,单凭传入的fn(fns[i] === fn)是删除不掉once订阅者的(因为传入的fn函数和once包装函数引用不相等),需要用到包装函数下的fn属性引用(fns.fn === fn)去识别订阅者才能进行删除。

EventEmitter完整源码:
https://github.com/booms21/Ev...

总结

以上就是发布订阅模式从实现到具体讲解的所有内容了。无论做什么事都是需要一些套路, 设计模式就是前人总结出来的编程套路,而学习这些套路就是进阶高级的必经之路,有了套路不仅避免了一些开发中的问题,还节省了许多浪费在思考造轮子的时间,事半功倍。


洛阳醉长安行
57 声望4 粉丝

charging...