经常会有面试题问发布订阅模式,很多童鞋都一脸懵逼,根本不知道在问什么,甚至觉得自己根本用不上这玩意。
其实,市面上的各大框架的事件处理,特别是组件通讯都基于发布订阅模式实现,比如vue的vuex,reace的redux。
那么什么是发布订阅模式?
发布订阅模式又叫观察者模式,它定义对象之间的一种一对多的依赖关系,当一个抽象的状态发生改变时,所有依赖它的对象都将得到通知。
下面我们用一个现实中例子来讲一下它们到底是什么。
小王去售楼部买房,但是该楼盘已售罄,于是售楼MM就告诉小王,我给你登记一下你的需求(订阅),等有房的时候我打电话通知你(发布)。
所以这就是一个发布订阅的简单例子,购房者只需要订阅自己的需求,当需求更新的时候,就会通知订阅者。
那么下面我们手写js,实现一个简单的发布订阅模式。
var salesOffices = {} // 定义售楼处
salesOffices.clientList = [] // 缓存列表,存放订阅者回调函数
// 订阅消息
salesOffices.listen = function (key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = [] // 如果没有订阅过该类型,则创建缓存列表
}
this.clientList[key].push(fn) // 订阅的消息存入缓存列表
}
// 发布消息
salesOffices.trigger = function () {
var key = Array.prototype.shift.call(arguments), // 取出消息类型 arguments为类数组
fns = this.clientList[key]; // 取出该类型对应的函数集合
if (!fns || fns.length === 0) {
return false // 如果没有订阅消息 则返回
}
for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments) // 代入参数arguments 执行
}
}
// 取消订阅
salesOffices.remove = function (key, fn) {
var fns = this.clientList[key]
if (!fns) {
return false // 如果没有订阅消息 则返回
}
if (!fn) {
fns && (fns.length = 0) // 如果没有传入具体回调函数,则表示取消该key下的所有订阅
} else {
for (var l = fns.length - 1; l >= 0; l--) {
var _fn = fns[l]
if (_fn === fn) {
fns.splice(l, i) // 删除订阅回调函数
}
}
}
}
// demo测试
salesOffices.listen('square88', function(price) {
console.log(`价格=${price}`)
})
salesOffices.trigger('square88', 200000)
以上版本,我们需要提前定义一个售楼处,但其实我们买房子的时候可以不用去售楼处,直接去中介那就可以了,所以我们对其进行改造,写一个全局的发布订阅模式。
var Event = (function () {
var clientList = {},
listen,
trigger,
remove;
listen = function (key, fn) {
if (!clientList[key]) {
clientList[key] = []
}
clientList[key].push(fn)
}
trigger = function () {
var key = Array.prototype.shift.call(arguments),
fns = clientList[key];
if (!fns || fns.length === 0) {
return false
}
for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments)
}
}
remove = function (key, fn) {
var fns = clientList[key]
if (!fns) {
return false
}
if (!fn) {
fns && (fns.length = 0)
} else {
for (var l = fns.length - 1; l >= 0; l--) {
var _fn = fns[l]
if (_fn === fn) {
fns.splice(l, 1)
}
}
}
}
return {
listen,
trigger,
remove
}
})()
Event.listen('square88', function (price) {
console.log(`价格是${price}`)
})
Event.trigger('square88', 10000)
总结:
通过对发布订阅模式的学习,我们很清楚能感受到它的优点。一为时间上的解耦,二为对象之间的解耦。但是它的缺点也很明显。每次创建订阅都会消耗时间和内存,如果该订阅一直没有触发,那么它依然会存在内存中,而且通过这种模式弱化了对象和对象间的联系,这让如果出了BUG,如何找到这个BUG也是让人头疼的事。