JavaScript设计模式(三):发布—订阅模式(观察者模式)

概述

发布—订阅模式又叫做观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象将得到通知。在JavaScript中,我们一般用事件模型来替代传统的发布—订阅模式。

  • 发布者状态变化自动通知订阅者
  • 发布者对象与订阅者对象松耦合地联系在一起

DOM事件

我们在 DOM 节点上绑定的事件函数,就是最典型的发布—订阅模式。

var event = new Event('alert')
document.body.addEventListener('alert', function(){
    alert('oops')
})
document.body.dispatchEvent(event) //模拟事件

我们无法预知用户何时会进行点击,因此我们订阅 body 上的 click 事件,当节点被点击时,body 节点便会向订阅者发布这个消息。

document.body.addEventListener('alert', function(){
    alert('emmm')
})

还可以添加多个订阅者,订阅者与发布者松耦合。

模式实现

JavaScript 中通过回调函数来实现发布—订阅模式。

  • 确定发布者
  • 存放订阅者回调函数的缓存列表
  • 方法:订阅 / 退订 / 发布
var publisher = {} // 定义发布者
publisher.subList = {} // 指定存放回调的缓存列表
publisher.listen = function(){
    //订阅
}
publisher.trigger = function(){
    //发布
}
publisher.remove = function(){
    //退订
}

也可是以下形式:

var publisher = {
    subList: {},
    listen: function(){},
    trigger: function(){},
    remove: function(){}
}

订阅

publisher.listen = function(type, fn){ // type:订阅类型 fn:订阅回调
    if(!this.subList[type]){ // 第一次订阅此类型时创建新缓存列表
        this.subList[type] = []
    }
    this.subList[type].push(fn) // 将订阅的回调添加至缓存列表
}

发布

publisher.trigger = function(){
    var type = [].shift.call(arguments), // 取得消息类型
        fns = this.subList[type] // 取得对应消息类型的回调 
    if( !fns || fns.length == 0 ){ // 无此类型或无回调直接返回
        return false
    }
    for(var i = 0; i < fns.length; i++){
        fns[i].apply(this, arguments) // 遍历回调并调用
    }
    return this // 链式调用
}

退订

publisher.remove = function(type, fn){ // 仅指定type时移除此类型的所有订阅
                                       // 同时指定fn时删除对应单个回调
    var fns = this.subList[type]
    if(!fns) return false // 如果无指定类型或无回调直接返回
    if(!fn){ // fn未指定
        fns && (fns.length = 0)
    }else{ // fn指定
        for(i = fns.length - 1; i >= 0; i--){ //反向遍历
            if(fn === fns[i]){
                fns.splice(i, 1) //将指定对调删除
            }
        }
    }
}

使用

publisher.listen('fruit', function(s){ // 订阅
    console.log('fruit: ' + s)
})
publisher.listen('fruit', fn1 = function(s){
    console.log('fruit#: ' + s)
})

publisher.trigger('fruit', 'banana') // 发布
publisher.remove('fruit', fn1) // 退订
publisher.trigger('fruit', 'apple')

"fruit: banana"
"fruit#: banana"
"fruit: apple"

原型模式实现

function Publisher(){
    this.subList = {}
}
Publisher.prototype = {
    listen: function(){},
    trigger: function(){},
    remove: function(){}
}

var p = new Publisher()
p.listen()

先发布后订阅

使用 setTimeout 将发布部分代码异步化,简单实现先发布后订阅的功能。

publisher.trigger = function(){
    var type = Array.prototype.shift.call(arguments),
        _this = this,
        args = arguments

    setTimeout(function(){
        var fns = _this.cache[type]
        if(!fns || fns.length==0)
            return false
        for(var i = 0; i < fns.length; i++){
            fns[i].apply(_this, args)
        }
    }, 0);
  return this
}

JS Bin - 发布订阅模式

posted @ 2017-12-05 21:26  tt273z  阅读(372)  评论(0编辑  收藏  举报