一、概念

1.定义

公布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送扭转时,所有依赖于它的对象都将失去状态扭转的告诉。

订阅者(Subscriber)把本人想订阅的事件注册(Subscribe)到调度核心(Event Channel),当发布者(Publisher)公布该事件(Publish Event)到调度核心,也就是该事件触发时,由调度核心对立调度(Fire Event)订阅者注册到调度核心的解决代码。

二、实现

1.实现思路

创立一个对象

  • 在该对象上创立一个缓存列表(调度核心Event Channel)
  • on办法用来把函数增加到缓存列表中(订阅者注册事件到调度核心)
  • emit办法取到argument里第一个当作event,依据event值去执行对应缓存列表中的函数(发布者公布事件到调度核心,调度核心解决代码)
  • off办法能够依据event值勾销订阅(勾销订阅)
  • once办法只监听一次,调用结束后删除缓存函数(订阅一次)

2.简略demo

class EventEmitter {    constructor() {        // 缓存列表        this.listener = {}    }    // 订阅    on(eventName, fn) {        // 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创立个缓存列表        // 如有对象中有相应的 event 值,把 fn 增加到对应 event 的缓存列表里        if(!this.listener[eventName]){            this.listener[eventName] = [];        }        this.listener[eventName].push(fn);    }    // 勾销订阅    off(eventName, fn) {        let callbacks = this.listener[eventName];        // 缓存列表中没有对应的fn,返回false        if(!callbacks){            return false;        }        if(!fn){            // 如果未传入fn,则将缓存列表中对应的fn都清空            callbacks && (callbacks.length = 0);        } else {            let cb;            // 遍历所对应的fn,判断和那个fn雷同,雷同则删除            for (let i = 0, cbLen = callbacks.length; i < cbLen; i++) {                cb = callbacks[i];                if (cb == fn || cb.fn == fn) {                    callbacks.splice(i, 1);                    break                }            }        }    }    // 监听一次    once(eventName, fn) {        // 先绑定,运行时删除对应的值        let on = () => {            this.off(eventName, on);            fn.apply(this, arguments);        }        on.fn = fn;        this.on(eventName, on);    }    // 公布    emit(eventName, data) {        const callbacks = this.listener[eventName];        if(callbacks) {            callbacks.forEach((c) => {                c(data);            })        }    }}let a = new EventEmitter();function aa(x) {    console.log(x);}a.on("kak", aa)a.on("kak", (data) => {    console.log("1", data);})a.emit('kak', 'hahahah');a.off('kak',aa);a.emit('kak', 'hahahah');

3.vue中的event Bus

function eventsMixin (Vue) {    var hookRE = /^hook:/;    Vue.prototype.$on = function (event, fn) {        var this$1 = this;    var vm = this;    // event 为数组时,循环执行 $on    if (Array.isArray(event)) {        for (var i = 0, l = event.length; i < l; i++) {            this$1.$on(event[i], fn);        }    } else {        (vm._events[event] || (vm._events[event] = [])).push(fn);        // optimize hook:event cost by using a boolean flag marked at registration         // instead of a hash lookup        if (hookRE.test(event)) {            vm._hasHookEvent = true;        }    }    return vm};Vue.prototype.$once = function (event, fn) {    var vm = this;    // 先绑定,后删除    function on () {        vm.$off(event, on);        fn.apply(vm, arguments);    }    on.fn = fn;    vm.$on(event, on);    return vm};Vue.prototype.$off = function (event, fn) {    var this$1 = this;    var vm = this;    // all,若没有传参数,清空所有订阅    if (!arguments.length) {        vm._events = Object.create(null);        return vm    }    // array of events,events 为数组时,循环执行 $off    if (Array.isArray(event)) {        for (var i = 0, l = event.length; i < l; i++) {            this$1.$off(event[i], fn);        }        return vm    }    // specific event    var cbs = vm._events[event];    if (!cbs) {        // 没有 cbs 间接 return this        return vm    }    if (!fn) {        // 若没有 handler,清空 event 对应的缓存列表        vm._events[event] = null;        return vm    }    if (fn) {        // specific handler,删除相应的 handler        var cb;        var i$1 = cbs.length;        while (i$1--) {            cb = cbs[i$1];            if (cb === fn || cb.fn === fn) {                cbs.splice(i$1, 1);                break            }        }    }    return vm};Vue.prototype.$emit = function (event) {    var vm = this;    {        // 传入的 event 辨别大小写,若不统一,有提醒        var lowerCaseEvent = event.toLowerCase();        if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {            tip(                "Event \"" + lowerCaseEvent + "\" is emitted in component " +                (formatComponentName(vm)) + " but the handler is registered for \"" + event + "\". " +                "Note that HTML attributes are case-insensitive and you cannot use " +                "v-on to listen to camelCase events when using in-DOM templates. " +                "You should probably use \"" + (hyphenate(event)) + "\" instead of \"" + event + "\"."            );        }    }    var cbs = vm._events[event];    if (cbs) {        cbs = cbs.length > 1 ? toArray(cbs) : cbs;        // 只取回调函数,不取 event        var args = toArray(arguments, 1);        for (var i = 0, l = cbs.length; i < l; i++) {            try {                cbs[i].apply(vm, args);            } catch (e) {                handleError(e, vm, ("event handler for \"" + event + "\""));            }        }    }    return vm};}/***   * Convert an Array-like object to a real Array.   */function toArray (list, start) {    start = start || 0;    var i = list.length - start;    var ret = new Array(i);    while (i--) {          ret[i] = list[i + start];    }    return ret} 

三、 总结

  1. 长处

    • 对象之间解耦
    • 异步编程中,能够更松耦合的代码编写
  2. 毛病

    • 创立订阅者自身要耗费肯定的工夫和内存
    • 尽管能够弱化对象之间的分割,多个发布者和订阅者嵌套一起的时候,程序难以跟踪保护

四、 扩大(公布-订阅模式与观察者模式的区别)

很多中央都说公布-订阅模式是观察者模式的别名,然而他们真的一样吗?是不一样的。

间接上图:

观察者模式:观察者(Observer)间接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。

公布订阅模式:订阅者(Subscriber)把本人想订阅的事件注册(Subscribe)到调度核心(Event Channel),当发布者(Publisher)公布该事件(Publish Event)到调度核心,也就是该事件触发时,由调度核心对立调度(Fire Event)订阅者注册到调度核心的解决代码。

差别

  • 在观察者模式中,观察者是晓得 Subject 的,Subject 始终放弃对观察者进行记录。然而,在公布订阅模式中,发布者和订阅者不晓得对方的存在。它们只有通过音讯代理进行通信。
  • 在公布订阅模式中,组件是涣散耦合的,正好和观察者模式相同。
  • 观察者模式大多数时候是同步的,比方当事件触发,Subject 就会去调用观察者的办法。而公布-订阅模式大多数时候是异步的(应用音讯队列)。
  • 观察者模式须要在单个应用程序地址空间中实现,而公布-订阅更像穿插利用模式。