前端面试——设计模式之发布-订阅模式

发布订阅模式(观察者模式)

定义:定义对象之间一对多的依赖关系,当一个对象状态发生改变时,所有依赖它的对象都将得到通知

发布-订阅模式让两个对象松耦合地联系在一起,虽然不太清楚彼此的细节,但这不影响它们之间相互通信。当有新的订阅者出现时,发布者的代码不需要做任何改变,同样发布者需求改变时,也不会影响到之前的订阅者。只要约定的事件名没有变化,就可以自由地改变它们

实现发布-订阅模式

  • 指定发布者
  • 给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者
  • 最后发布消息的时候,发布者遍历缓存列表,依次触发里面存放的订阅者回调函数
  • 取消订阅:删掉缓存列表中对应的回调函数
通用的发布-订阅模式

先把发布-订阅的功能提取出来,放在一个单独的对象内

const event={
    clientList:{},
    listen:function(key,fn){
        if(!this.clientList[key]){
            this.clientList[key]=[];
        }
        this.clientList[key].push(fn);//订阅的消息加入缓存列表
    },
    trigger:function(){
        const key=Array.prototype.shift.call(arguments);
        const fns=this.clientList[key];

        //如果没有绑定对应的消息
        if(!fns||fns.length===0) return false;

        for(let i=0,fn;fn=fns[i++];){
            fn.apply(this,arguments);//arguments是调用trigger时所带上的参数
        }
    }
}

再定义一个installEvent函数,这个函数可以给所有对象都动态安装发布-订阅功能

const installEvent=function(obj){
    for(let key in event){
        obj[key]=event[key];
    }
}

测试——给售楼处对象salesOffice动态添加发布-订阅功能

const salesOffices={};
installEvent(salesOffices);

salesOffices.listen('square88',function(price){//小明订阅消息
    console.log(`价格:${price}`);
});

salesOffices.listen('square110',function(price){//小红订阅消息
    console.log(`价格:${price}`);
});

salesOffices.trigger('square88',880000);//价格:880000
salesOffices.trigger('square110',1100000);//价格:1100000
取消订阅的事件
event.remove=function(key,fn){//取消订阅
    const fns=this.clientList[key];
    //如果对应的key值没人订阅,直接返回
    if(!fns) return false;
    //如果没有传入回调函数,表示要取消key值对应消息的所有订阅
    if(!fn){
        fns&&(fns.length=0);
    }else{
        //反向遍历订阅的回调函数列表
        for(let i=fns.length-1;i>=0;i--){
            if(fns[i]===fn){
                //删除订阅者的回调函数
                fns.splice(i,1);
            }
        }                    
    }
}

测试:

const fn=function(price){
    console.log(`价格:${price}`);
}

salesOffices.listen('square88',fn);//订阅
salesOffices.remove('square88');//取消订阅
全局的发布-订阅对象

发布-订阅模式中,可以用一个全局的Event对象来实现,订阅者不需要了解信息来自哪个发布者,发布者也不知道信息会推送给哪个订阅者,Event作为一个类似“中介”的角色,把发布者和订阅者联系起来

//全局的发布-订阅对象
const Event=(function(){
    const clientList={};

    //订阅消息
    const listen=function(...args){
        const key=args[0];
        const fn=args[1];
        if(!clientList[key]){
            clientList[key]=[];
        }
        clientList[key].push(fn);//订阅的消息加入缓存列表
    }

    //发布消息
    const trigger=function(){
        const key=Array.prototype.shift.call(arguments);
        const fns=clientList[key];

        //如果没有绑定对应的消息
        if(!fns||fns.length===0) return 0;

        for(let i=0,fn;fn=fns[i++];){
            fn.apply(this,arguments);//arguments是调用trigger传入的参数
        }
    }

    //取消订阅
    const remove=function(...args){
        const key=args[0],fn=args[1];
        const fns=clientList[key];
        //未订阅,直接返回
        if(!fns||fns.length===0) return false;
        //未传入fn,取消key所有相关的订阅
        if(!fn){
            fns&&(fns.length=0);
        }else{
            //反向遍历缓存列表
            for(let i=fns.length-1;i>=0;i--){
                if(fns[i]===fn){
                    //从缓存列表删除订阅者的回调函数
                    fns.splice(i,1);
                }
            }
        }
    }

    return {
        listen,
        trigger,
        remove
    }
})();


const fn=function(price){
    console.log(`价格:${price}`);
}
Event.listen('square90',fn);
Event.listen('square10',fn);

Event.remove('square10',fn);

Event.trigger('square90',1000);
Event.trigger('square10',3000);

//输出:价格:1000

模块间通信

点击a模块里的按钮,b模块显示按钮点击次数:

const a=(function(){
    let count=0;
    const btn=document.querySelector('#btn');
    btn.addEventListener('click',function(){
        Event.trigger('add',++count);
    });
})();

const b=(function(){
    const divObj=document.querySelector('#text');
    Event.listen('add',function(count){
        divObj.innerHTML=count;
    })
})();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CfrE5Twv-1637981929666)(C:\Users\hp\AppData\Roaming\Typora\typora-user-images\image-20211113113537094.png)]

先发布再订阅

某些情况下,我们需要先将消息保存下来,等到有对象订阅它的时候,再重新安发布给订阅者。就像QQ的离线消息一样,离线消息先被保存在服务器中,接收人下次登录的时候,可以重新接收到这条消息

为了满足这类需求,可以建立一个存放离线事件的堆栈,事件发布的时候,还没有订阅者来订阅这个事件,就暂时先把发布事件的动作包裹在一个函数里,这些包装函数将会被保存在堆栈中,等到有订阅者订阅消息的时候,再遍历堆栈并且依次执行这些包装函数,也就是重新发布这些事件(离线事件生命周期只有一次,就像qq消息只会被重新阅读一次)

总结

发布-订阅模式,又称观察者模式,在实际开发中非常有用

优点:

  • 时间上的解耦
  • 对象之间的解耦

缺点:

  • 创建订阅者本身需要消耗一定的时间和内存,而当你订阅一个消息后,也许消息最后都没有发生,但这个订阅者会始终存在于内存中

  • 可以弱化对象之间的联系,如果过度使用的话,对象之间的必要联系会被深埋在背后,会导致程序难以跟踪维护和理解,特别是多个发布者和订阅者嵌套在一起的时候

发布-订阅模式的应用非常广泛,既可以用在异步编程中,也可以帮助我们完成更松耦合的代码编写,还可以用来帮助实现一些别的设计模式,比如中介者模式

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值