订阅-发布模式 && 回调 && 钩子函数
《JavaScript设计模式于开发实践》曾探,读书笔记
发布-订阅模式又叫做观察者模式,它定义了对象间一种一对多的关系,当一个对象发生改变的时候,所有依赖于它的对象都将得到通知。
发布—订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。
从售楼的例子讲解回调于订阅-发布
小明有了钱想买房子所以来到了售楼部,售楼部小姐姐给小明说现有的房子已经没了。马上有一处的楼盘要开通了,但是具体是什么时候小明不知道。
- 第一种情况,以后小明每天都会打电话给售楼部,问楼盘情况。主动权在小明处,小明有权决定什么时候打电话(执行打电话这个回调,然后得到楼盘情况)。
- 小姐姐留下了小明的电话,然后当楼盘可以出售,然后给小明打电话。(主动权在售楼部)
dom事件
在dom上绑定事件就是一个订阅-发布的模式。
自定义事件
- 指定发布者(售楼部)
- 发布者设置缓存列表,用于存放回调函数方便通知订阅者(售楼部的花名册)
- 发布消息的时候,遍历花名册,通知到每个人。
下面是一个售楼部通知客户的简单实现
// 售楼部
var salesOffice = {}
// 用户短信名单
salesOffice.clientList = []
// 订阅事件
salesOffice.listen = function(fn) {
this.clientList.push(fn)
}
// 发布消息
salesOffice.trigger = function() {
for(var i = 0, fn; fn = this.clientList[i++];) {
fn.apply(this, arguments)
}
}
// 用户订阅消息
salesOffice.listen(function(price) {
console.log('用户1', price)
})
salesOffice.listen(function(price) {
console.log('用户2', price)
})
salesOffice.trigger(120000)
复制代码
- 以上代码有个问题,没有实现我们的选择性订阅,而是一股脑的把所有消息给了用户。
- 如果这个售楼部又A,B两个楼盘,那么怎么实现选择性的定于了
// 售楼部
var salesOffice = {}
// 用户短信名单
salesOffice.clientList = {}
// 订阅事件,使用key来丝线自定义订阅
salesOffice.listen = function(key,fn) {
if(!this.clientList[key]) this.clientList[key] = []
this.clientList[key].push(fn)
}
// 发布消息,使用key来发布特定消息
salesOffice.trigger = function(key) {
for(var i = 0, fn; fn = this.clientList[key][i++];) {
// 去掉第一个参数
let args = Array.from(arguments)
args.shift()
fn.apply(this, args)
}
}
// 用户订阅消息
// 订阅了A楼盘的信息
salesOffice.listen('A',function(price, size) {
console.log('用户1', price, size)
})
// 订阅了B楼盘的信息
salesOffice.listen('B',function(price, size) {
console.log('用户2', price, size)
})
salesOffice.trigger('B',120000, 80)
复制代码
上面代码是不是于dom事件绑定十分相似了
document.body.addEventListener('click', function(){
console.log('click')
})
复制代码
通用的订阅-发布
以上就是比较完整的订阅-发布的功能。但是我们还可以更加通用。如果又另一个售楼部又要实现这个功能,就需要把代码重新写一遍。所以我们需要把他变得更加通用。
// 抽离订阅-发布功能
var event = {
clientList: {},
listen: function(key,fn) {
if(!this.clientList[key]) this.clientList[key] = []
this.clientList[key].push(fn)
},
trigger: function(key) {
for(var i = 0, fn; fn = this.clientList[key][i++];) {
// 去掉第一个参数
let args = Array.from(arguments)
args.shift()
fn.apply(this, args)
}
}
}
// 不同的售楼部 并且安装 订阅-发布功能
var salesOfficeA = {...event} // 售楼部A
var salesOfficeB = {...event} // 售楼部B
// 用户A 订阅 售楼部A的A楼盘
salesOfficeA.listen('A', function(price, size){
console.log('用户A', price, size)
})
// 用户B 订阅 售楼部B的B楼盘
salesOfficeB.listen('B', function(price, size){
console.log('用户B', price, size)
})
// 售楼部A发布A楼盘的信息
salesOfficeA.trigger('A', 120000, 89)
复制代码
取消订阅
event.remove: function(key, fn) {
var fns = this.clientList[key]
for(var i = 0; i < fns.length; i++) {
if(fns[i] === fn) {
fns.splice(i, 1)
}
}
}
// 不同的售楼部 并且安装 订阅-发布功能
var salesOfficeA = {...event} // 售楼部A
var salesOfficeB = {...event} // 售楼部B
// 用户A 订阅 售楼部A的A楼盘
let fn = function(price, size){
console.log('用户A', price, size)
}
salesOfficeA.listen('A', fn)
复制代码
注意 订阅于取消订阅是同一个fn
实际例子对比回调于发布-订阅
有如下功能
- 登陆
- 登陆成功之后得到用户信息
- 得到用户信息渲染header
- 渲染conent内容板块
- 第一种使用回调的方式写法如下:
login.succ(function(data){
header.setHeader(data)
conent.setContent(data)
})
复制代码
这种方式是回调的方式,不利于代码扩展。以后如果foot又徐哟啊渲染,还得回到这个地方重新修改。
- 发布-订阅的方式
$.ajax(url, function(data) => {
// 发布登陆成功的消息
login.trigger(data)
})
var header = (function(){
// 订阅
login.listen('succ', function(data) => {
header.setHeader(data)
})
return {
setHeader: function(data) {
// 具体渲染header的代码
}
}
})()
复制代码
通过对比发布-订阅方式的耦合性没那么强。
钩子函数
我们常常说的钩子函数就是通过发布-订阅的方式实现的。他于回调的区别已经对比以上已经对其有所说明了