设计模式之发布 — 订阅模式

2021-08-16  本文已影响0人  zhao_ran

发布 — 订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

DOM 事件

实际上,只要我们曾经在 DOM节点上面绑定过事件函数,那我们就曾经使用过发布 — 订阅模式,来看看下面这两句简单的代码发生了什么事情···

document.addEventListener('click',function(){
    alert(1);
})

document.body.click();

在这里需要监控用户点击document.body的动作,但是我们没办法预知用户将在什么时候点击。所以我们订阅 document.body上的 click 事件,当body节点被点击时,body节点便会向订阅者发布这个消息。这很像购房的例子,购房者不知道房子什么时候开售,于是他在订阅消息后等待售楼处发布消息。

自定义事件

除了DOM 事件,我们还会经常实现一些自定义的事件,这种依靠自定义事件完成的发布 —订阅模式可以用于任何JavaScript代码中。
现在看看如何一步步实现发布 — 订阅模式。
1.首先要指定好谁充当发布者(比如售楼处);
2.然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者(售楼处的花名册);
3.最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数(遍历花名册,挨个发短信)。

var salesOffices = {}; //售楼部

salesOffices.clientList = {}; // 缓存列表

salesOffices.listen = function(key,fn){
    if(!this.clientList[key]){
        this.clientList[key] = [];
    }
    this.clientList[key].push(fn);
}

salesOffices.trigger = function(){
    var key = Array.prototype.shift.call(arguments); // 取出消息key
    var fns = this.clientList[key];

    if( !fns || fns.length === 0 ){
        return false;
    }

    for(var i=0; i<fns.length; i++){
        fns[i].apply(this,arguments);  // arguments是发布消息的的时候带的参数
    }
}

// 测试
salesOffices.listen('seqaureMetar88', function(price){
    console.log('seqaureMetar88 价格:' +price);
})

salesOffices.listen('seqaureMetar88', function(price){
    console.log('zz seqaureMetar88 价格:' +price);
})

salesOffices.listen('seqaureMetar110', function(price){
    console.log('seqaureMetar110 价格:' +price);
})

salesOffices.trigger('seqaureMetar88', 20000);
salesOffices.trigger('seqaureMetar110', 30000);

发布-订阅模式的通用实现

我们先抽出来放到一个对象中

var event = {
    clientList: {},
    listen: function(key,fn){
        if(!this.clientList[key]){
            this.clientList[key] = [];
        }
        this.clientList[key].push(fn);
    },
    trigger: function(){
        var key = Array.prototype.shift.call(arguments); // 取出消息key
        var fns = this.clientList[key];
        if( !fns || fns.length === 0 ){
            return false;
        }
        for(var i=0; i<fns.length; i++){
            fns[i].apply(this,arguments);  // arguments是发布消息的的时候带的参数
        }
    }
}

在定义一个installEvent函数,可以给所有函数安装发布-订阅功能(也可以用继承)

var installEvent = function(obj){
    for(var i in event){
        obj[i] = event[i]
    }
}

// 测试
var salesOffices = {};
installEvent(salesOffices);

salesOffices.listen('seqaureMetar88', function(price){
    console.log('seqaureMetar88 价格:' +price);
})

salesOffices.listen('seqaureMetar88', function(price){
    console.log('zz seqaureMetar88 价格:' +price);
})

salesOffices.listen('seqaureMetar110', function(price){
    console.log('seqaureMetar110 价格:' +price);
})

salesOffices.trigger('seqaureMetar88', 20000);  // seqaureMetar88 价格:20000  seqaureMetar88 价格:20000
salesOffices.trigger('seqaureMetar110', 30000); // seqaureMetar110 价格:30000
小结

发布 — 订阅模式的优点非常明显,一为时间上的解耦,二为对象之间的解耦。它的应用非常广泛,既可以用在异步编程中,也可以帮助我们完成更松耦合的代码编写。发布 — 订阅模式还可以用来帮助实现一些别的设计模式,比如中介者模式。 从架构上来看,无论是MVC还是MVVM,都少不了发布 — 订阅模式的参与,而且JavaScript本身也是一门基于事件驱动的语言。

当然,发布 — 订阅模式也不是完全没有缺点。创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中。另外,发布 — 订阅模式虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一起的时候,要跟踪一个 bug不是件轻松的事情。

上一篇下一篇

猜你喜欢

热点阅读