JavaScript设计模式学习笔记(一)——发布-订阅模式

363 阅读2分钟

**定义:**发布-订阅模式又称观察者模式,它定义了对象间一对多的关系,当一个对象的状态发生变化,它就会通知订阅了它的对象。

举个例子:小明去了某个商场买手机,但是到了以后才发现自己想买的手机没货了,不过店家说过几天会到货,但是什么时候到货目前还不能确认,为了确保第一时间买到心仪的手机,小明把手机留给了店家,让店家有货的时候通知他。同时,还有张三、李四等人也留下了电话给店家。

用代码实现下这个简单的例子

var shopObj = {} // 定义商店对象 
shopObj.clientObj = {} // 缓存对象,用于存放订阅客户的回调    

// 定义订阅方法    
shopObj.listen = function (key, cb) { 
    // key:订阅的手机款式, cb: 订阅者的回调,用于通知客户所需信息        
    if (!this.clientObj[key]) {            
        this.clientObj[key] = [] // 若还没订阅该款式的手机,以该手机款式创建新的缓存列表
    }        
    this.clientObj[key].push(cb)    
}    

// 定义发布通知方法    
shopObj.notice = function () {        
    var key = Array.prototype.shift.call(arguments) // 获取手机款式key值        
    var args = arguments        
    var cbs = this.clientObj[key] // 对应手机款式的订阅者回调            
    if (!cbs || cbs.length === 0) { 
        // 没有在订阅对象内直接return            
        return        
    }        
    for (var i = 0; i < cbs.length; i++) {            
        cbs[i].apply(this, arguments) // arguments为店家发布时附加的商品参数,如型号、价格等
    }    
}    

// 客户订阅    
shopObj.listen('iPhone12', function (color, size) { // 小明订阅了iPhone12推送            
    console.log('iPhone12:', color , '内存', size , '到货了!')    
})    
shopObj.listen('iPhone12', function (price, size) { // 小红订阅了iPhone12推送                
    console.log('iPhone12:', price + '元', size, '到货了!')   
})  
shopObj.listen('HUAWEIP40', function (size, price) { // 张三订阅了华为P40内存大小、价格推送            
    console.log('HUAWEIP40:', size , price + '元', '到货了!')    
})    

// 商家发布通知给对应的订阅者    
shopObj.notice('iPhone12', '蓝色', '128g') // 发布iPhone12推送    
shopObj.notice('HUAWEIP40', '258g', '8888') // 发布HUAWEIP40推送

通过这个简单的例子实现了发布-订阅模式,例子中加入了clientObj缓存对象,使得每个客户只会收到自己关心的手机款式的推送通告。

**应用场景:**例如在一个网购平台上面,如果需要用户登录以后会执行各种操作,如网购平台在登录后会根据用户历史行为重新刷新推送列表,这时候如果是传统做法可能是一层一层的发布事件或者传递参数告诉列表做一个刷新的动作,这样做的缺点就是代码的耦合度很高,必须在登录逻辑里面进行修改代码,如果后续登录以后会执行更多行为,则登录模块就会代码很复杂和不易阅读。而使用发布-订阅模式的思路就是,我向登录模块订阅一个事件,等登录的动作完成后发布,执行订阅的行为

// 传统方法
loginModel.login((res) => {
    if (res.success) {
        // todo 发事件或传参数通知列表刷新
        list.refreshList()
        // todo 改变主题样式 
        index.setStyle()
        // todo 一堆操作。。。
    } 
})

// 发布-订阅方式
loginModel.login((res) => {
    if (res.success) {
        // 发布订阅通知
        loginModel.notice('loginSuccess', data)
    } 
})// 列表模块
loginModel.listen('loginSuceess', function(data){
    // todo 刷新列表
})

// 主题模块
loginModel.listen('loginSuceess', function(data){
    // todo 改变主题样式
})

// 如此类推,其他有订阅到'loginSuccess'的都会被通知到,每个模块只需要负责完成自己的行为就可以了

**小结:**发布-订阅者模式优点:通过事件驱动进行降低对象之间的耦合度,订阅者无需主动监听的对象的变化,而是通过回调函数通知自己,例如vue中的双向绑定的原理就是利用了发布-订阅模式实现的。另外就是降低了时间上的耦合度,它能应用到异步编程当中,如执行完某个异步请求后再通知其他模块做相应的操作。但是发布-订阅模式也是有缺陷的,因为创建订阅者时就需要把订阅者的回调存到订阅对象中,尽管这个订阅信息不一定会有回应,但它已经存在订阅对象中了,所以过度使用会耗费内存。