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
}