【设计模式】Javascript设计模式——订阅发布模式

时间:2020-07-01 12:44:48   收藏:0   阅读:70

一、什么是订阅/发布模式

发布-订阅模式又叫观察者模式

凡是以上边这句话开头的基本都是没理解订阅-发布模式和观察者模式。

订阅/发布模式:发布/订阅模式属于设计模式中的行为(Behavioral Patterns),其中包含发布者(Publisher)订阅者(Subscriber)以及一个调度中心(Event Channel),发布者和订阅者彼此不需要互相认识,订阅者把自己想订阅的事件注册到调度中心,当发布者发布该事件到调度中心,也就是该事件触发时,由调度中心统一调度订阅者注册到调度中心的处理代码。比如你微信里边订阅得每一个公众号,当你关注这个公众号之后,每当公众号有新得文章发布,就会有消息通知到你,其中你就属于订阅者(Subscriber),公众号就是发布者(Publisher),微信公众号服务中心就是(Event Channel),它会即使把发布者得信息通知到订阅者。

关于观察者模式,链接在这里。

二、有什么用

? 最开始听说订阅/发布模式还是在Vue的数据双向绑定原理中:通过数据劫持结合发布-订阅模式的方式来实现的。以下订阅发布模式的实际应用,后续又读到相关类似代码添加

三、核心点

四、Coding

简易版订阅发布

var pub_sub = {//调度中心
  events: {}, //事件中心
  subscribe(key, func) {//添加订阅事件
    if(!key || !func){
        console.log("订阅失败,key:"+key+",func:"+func)
        return false;
    }
    if (this.events[key]) {//是否有这个订阅事件
      this.events[key].push(func); //已有订阅事件添加回调函数
    } else {
      this.events[key] = [func]; //新添加订阅事件
    }
    console.log("订阅成功,key:"+key+",func:"+func)
    return true;
  },
  publish(event, ...args) {//发布事件
    let subscribedEvents = this.events[event]; // 取出所有订阅者的回调函数
    if (subscribedEvents && subscribedEvents.length) {
      subscribedEvents.forEach((callback) => {//遍历所有的回调
        if(typeof callback === `function`){
            callback.call(this, ...args);
        }
      });
    }
  },
  unsubscribe(event, func) {// 删除某个订阅,保留其他订阅
    let subscribedEvents = this.events[event];
    if (subscribedEvents && subscribedEvents.length) {
      this.events[event] = this.events[event].filter(//过滤把同名的删除
        (cb) => cb !== func
      );
    }
  }
};
function user1(){
    console.log("我user1订阅价格变更通知,收到的信息:"+[...arguments]);
}
function user2(){
    console.log("我user2订阅价格变更通知,收到的信息:"+[...arguments]);
}
function user3(){
    console.log("我user3订阅价格变更通知,收到的信息:"+[...arguments]);
}
pub_sub.subscribe("priceChange",user1);
pub_sub.subscribe("priceChange",user2);
pub_sub.subscribe("priceChange",user3);
pub_sub.publish("priceChange","价格降下来了,来买吧");
console.log("---user2退订---");
pub_sub.unsubscribe("priceChange",user2);
pub_sub.publish("priceChange","价格涨了,最近别买了");

Vue$on$once$off $emit的源码

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
            // 优化 hook:event, 如果 _hasHookEvent 为 true,那么在触发各类生命周期钩子的时候会触发
            // 如 hook:priceChange 事件
            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
}

五、小结

订阅/发布模式和观察者模式的区别如下图所示:

技术图片

订阅发布的核心点是Event Channel调度中心以及发布者和订阅者的完全解耦

观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录

优势

劣势

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!