JavaScript设计模式系列--发布订阅模式

栏目: JavaScript · 发布时间: 6年前

内容简介:发布订阅模式是JavaScript设计模式系列中什么是设计模式?什么是设计模式系列中的发布订阅模式?

发布订阅模式是JavaScript设计模式系列中 特别重要的一种,特别重要,特别重要 ···

思考一下

什么是设计模式?

  • 设计模式是 前人总结的用于解决开发过程中某类问题的方法

什么是 设计模式 系列中的发布订阅模式?

往下看···

理解“发布”和“订阅”

有些文章中介绍发布订阅模式是喜欢用一些案例来描述,这些案例很容易理解,但是把这些案例转换到程序描述的过程中很容易产生晕晕的感觉。那么在这篇文章中我们从概念的角度开始逐步分析“发布订阅模式”。

什么是“发布”?

“发布”,一般是指发布信息。在现实社会中怎么发布信息呢?比如用你们村里的大喇叭广播你马上要结婚了,让全村的人都知道。 你也可以写N封婚礼邀请函,直接交给N个你想让他们参加你婚礼的人,其他未收到邀请函的人就不知道你举办婚礼的事。发布信息的方式有很多很多 ···

什么是“订阅”?

“订阅”,预订阅览,一般是指订阅某种东西,比如你订阅了一份新华日报,那么每天某个固定时间点你就能收到邮递员给你送来的报纸。换方式去理解就是你想定时收取某种信息,那么信息就会在那个时间点送到你身边。

我们人类这么聪明当然会想到各种方法去发布和订阅自己的信息,那么 计算机程序该怎么“发布”和“订阅”它的信息呢?

下面我们以JavaScript程序为例,模拟现实社会中的“发布订阅”信息的过程。那么这个时候就要用面向对象的思想来分析这个过程。首先我们线对“发布订阅”这个过程进行程序建模。

// 发布订阅模型
var Publisher = {
    watchers: { // 已经订阅的事件, 每个事件类型的值是一个数组,用来存放该事件下需要触发的所有回调函数
        'tpye1': [cb1, cb2 ...],
        'type1': [cb1, cb2 ...],
    }, // 
    addWatcher: function(type, cb) { //添加订阅者,订阅者其实就是添加了相应的事件及其被触发时对应的回调函数
        // type 订阅类型
        // cb 回调函数
        
        // 这里将相应的事件type以及其对应的cb存入到this.wathers对象里面
    },
    removeWatcher: function(type, cb) { // 删除订阅者
        // type 订阅类型
        // cb 回调函数
        
        // 这里将相应的事件type以及其对应的cb从this.wathers对象里面删除
    },
    on: function() { // 监听,然后对所有订阅了该type的订阅者发布消息,发布消息其实就是触发对应的回调函数
        // 此处可以使用arguments属性获取其参数
        
        // 这里要触发对应type的回调函数
    }
}
复制代码

为了理解起来方便,上面程序仅仅是建立的发布订阅模型,是不是很简单?下面我们用Js来完善这个模型使其能够工作。

// 发布者类
class Publisher {
    constructor() {
        this.watchers = {};
    }
    //添加订阅者,订阅者其实就是添加了相应的事件及其被触发时对应的回调函数
    addWatcher(type, cb) {
    	if(!this.watchers[type]) {
    	    this.watchers[type] = []
    	}
    	this.watchers[type].push(cb);
    }
    // 删除订阅者
    removeWatcher(type, cb) {
        var cbs = this.watchers[type]; // 取出该类型对应的消息集合
    	if(!cbs) {
    	   return false;
    	}
    	if(!cb) {
    	    cbs && (cbs.length = 0);
    	}else {
    	    for(var i=0; i<cbs.length; i++) {
    		if(cb === cbs[i]) {
    		    cbs.splice(i, 1);
    		}
    	    }
    	}
    }
    // 监听,有点程序中会用trigger名,然后对所有订阅了该type的订阅者发布消息,这个过程根据type就是触发对应的回调函数
    on() {
    	var type = [].shift.call(arguments);
    	var cbs = this.watchers[type];
    	if(!cbs || cbs.length == 0) {
    	    return false;
    	}
    	for(var i=0; i<cbs.length; i++) {
    	    cbs[i].apply(this, arguments);
    	}
    }
}
// 发布者实体对象
var publishObj = new Publisher();
// 添加订阅type为'console'
publishObj.addWatcher('console', function() {
    var msg = [].shift.call(arguments);
    console.log(msg);
});
// 添加订阅type为'alert'
publishObj.addWatcher('alert', function() {
    var msg = [].shift.call(arguments);
    alert(msg);
});

publishObj.on('console', '触发console 1!');
publishObj.on('console', '触发console 2!');
publishObj.on('alert', '触发alert!');
publishObj.removeWatcher('alert', cb2); // 注意这里是按照地址引用的。如果传入匿名函数则删除不了 
publishObj.on('alert', '触发alert!');
复制代码

上面是发布订阅模式的基本程序案例,基于这个案例我们可以拓展出很多常见的应用,请看下文。

发布订阅模式应用案例

Vue - EventBus

《面试官:既然React/Vue可以用Event Bus进行组件通信,你可以实现下吗?》 这篇文章中作者由浅入深的介绍了实现EventBus的思路,并给出了相应JavaScript实现程序。我们仔细分析代码就能发现EventBus的实现就是基于发布订阅模式。下面我们引用一下该文章中的程序,简单做了下改造(将 prototype 上方法的实现直接放从class内部)。

提前声明:我们没有对传入的参数进行及时判断而规避错误,仅仅对核心方法进行了实现。

class EventEmeitter {
    constructor() {
        this.events = this.events || new Map();
        this.maxListeners = this.maxListeners || 10;
    }
    // 监听,然后对所有订阅了该type的订阅者发布消息,与上面不同的是这里的type后面为参数非回调函数
    emit(type, ...args) {
        let handler;
        handler = this.events.get(type);
        if(Array.isArray(handler)) {
            for(let i=0; i<handler.length;i++) {
                if(args.length > 0) {
                    handler[i].apply(this, args);
                }else {
                    handler[i].call(this);
                }
            }
        }else {
            if(args.length > 0) {
                handler.apply(this, args);
            }else {
                handler.call(this);
            }
        }
    }
    // 添加订阅事件类型
    addListener(type, callback) {
        const handler = this.emit.get(type);
        if(!handler) {
            this.events.set(type, callback)
        }else if(handler && typeof handler === 'function') {
            this.events.set(type, [handler, callback]);
        }else {
            handler.push(callback);
        }
    }
    // 删除订阅事件类型
    removeListener(type, callback) {
        var handler = this.events.get(type);
        if(handler && typeof handler === 'function') {
            this.events.delete(type, callback);
        }else {
            let position;
            for(let i=0; i<handler.length; i++) {
                if(handler[i] === callback) {
                    position = i;
                }else {
                    position = -1;
                }
            }
            if(position !== -1) {
                handler.splice(i, 1);
                if(handler.length == 1) {
                    this.events.set(type, handler[0])
                }
            }else {
                return this;
            }
        }
    }
}
复制代码

EventBus实现的过程基本和上文中介绍的发布订阅模式思路一致,仅仅是具体业务处理逻辑不同。

Vue - 双向绑定/数据劫持--发布订阅

先看一下Vue实现双向数据绑定的程序,其主要思想是observer每个对象的属性,添加到订阅器dep中,当数据发生变化的时候发出notice通知。 相关源代码(为方便阅读已经去掉flow部分)如下:(作者采用的是ES6+flow写的,代码在src/core/observer/index.js模块里面)。

export function defineReactive(obj, key, val, customSetter, shallow) {
    const dep = new Dep(); // 创建订阅对象
    const property = Object.getOwnPropertyDescriptor(obj, key); // 获取当前对象自身属性描述,返回值为对象(有多个描述属性),原型上属性无法获取
    if(property && property.configurable === false) {
        return
    }

    // cater for pre-defined getter/setters
    const getter = property && property.get
    const setter = property && property.set

    if((!getter || setter) && arguments.length === 2) {
        val = obj[key]
    }

    let childOb = !shallow && observe(val); // 创建一个观察者对象

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurale: true,
        get: function reactiveGetter() {
            const value = getter ? getter.call(obj) : val
            if(Dep.target) {
                dep.depend()
                if(childOb) {
                    childOb.dep.depend()
                    if(Array.isArray(value)) {
                        dependArray(value)
                    }
                }
            }
            return value;
        },
        set: function reactiveSetter(newVal) {
            const value = getter ? getter.call(obj) : val

            if(newVal === value || (newVal !== newVal && value !== value)) {
                return
            }

            if(ProcessingInstruction.env.NODE_ENV !== 'production' && customSetter) {
                customSetter();
            }

            if(setter) {
                setter.call(obj, newVal)
            }else {
                val = newVal
            }
            childOb = !shallow && observe(newVal) // 继续监听新的属性值
            dep.notify() // 这个是真正劫持的目的,要对订阅者发布通知了
        }
    });
}
复制代码

上面程序是双向数据绑定/数据劫持的部分,下面我们看一下订阅者对象也就是 Dep 的实现源码。

export default class Dep {
    constructor() {
        this.id = uid++;
        this.subs = []
    }
    // 添加订阅
    addSub(sub) {
        this.subs.push(sub);
    }
    // 删除订阅
    removeSub(sub) {
        remove(this.subs, sub)
    }
    // 
    depend() {
        if(Dep.target) {
            Dep.target.addDep(this)
        }
    }
    // 发布消息
    notify() {
        const subs = this.subs.slice();
        if(ProcessingInstruction.env.NODE_ENV !== 'production' && !config.async) {
            subs.sort((a, b) => a.id - b.id)
        }

        for(let i=0, l=subs.length; i<l; i++) {
            subs[i].update()
        }
    }
}
复制代码

vue中发布订阅模式应用在上文中做了简单介绍,后续会有文章专门介绍vue原理。

总结

发布订阅模式的核心过程其实分为两步,一是添加订阅也就是添加监听事件及对应的方法,二是发布消息也就是根据事件类型触发相应的方法。

so,你可以根据发布订阅模式的原理联想到更多的实际业务问题吗?

参考文章


以上所述就是小编给大家介绍的《JavaScript设计模式系列--发布订阅模式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java Message Service API Tutorial and Reference

Java Message Service API Tutorial and Reference

Hapner, Mark; Burridge, Rich; Sharma, Rahul / 2002-2 / $ 56.49

Java Message Service (JMS) represents a powerful solution for communicating between Java enterprise applications, software components, and legacy systems. In this authoritative tutorial and comprehens......一起来看看 《Java Message Service API Tutorial and Reference》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具