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
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· DeepSeek火爆全网,官网宕机?本地部署一个随便玩「LLM探索」
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 上周热点回顾(1.20-1.26)
· 【译】.NET 升级助手现在支持升级到集中式包管理