什么是发布/订阅者模式?
相信大家在看Vue的视图更新以及$emit事件源码都有见到发布订阅的身影。包括Nodejs Events、jQuery中同样都有实现发布订阅模式,用网上一张比较形象的图:
左边是观察者模式,右边是发布订阅模式:定义有事件名的若干个订阅者集合进行维护(增删改查),在发布(emit)的时候通过唯一的事件名(key)把对应的订阅者集合中的方法逐个依次执行一遍。
具体实现
var eventEmitter = {
list: {},
//订阅主题
on: function (event, fn) {
if (typeof fn !== "function") {
return false;
}
//创建订阅者列表,如果存在就直接插入
(this.list[event] || (this.list[event] = [])).push(fn);
return this;
},
//发布主题
emit: function () {
var event = [].shift.call(arguments);
if (this.list[event] && this.list[event].length) {
var fns = this.list[event].slice();
//浅拷贝后直接对列表所有订阅者函数依次执行
for (var i in fns) {
this.list[event][i].apply(this, arguments);
}
return this;
}
return false;
},
}
on
和emit
的话比较简单
首先定义一个list
对象用于存放事件的集合的映射表
当调用on
事件绑定的时候通过传入的事件名判断当前是否已存在list
中,不存在则先设置一个空数组,否则就直接push进去。
emit
发布执行对应事件event对入参arguments进行处理(shift剪出要触发的事件名),通过事件名先浅拷贝一个列表副本,然后遍历执行对应列表的所有的函数this.list[event][i].apply(this, arguments)
//移除对应订阅者
remove: function (event, fn) {
var fns = this.list[event];
if (!fns) return false;
//如没传递对应的订阅者函数引用,就默认删除整个事件列表
if (!fn) {
delete this.list[event];
return this;
}
//找到对应的订阅者进行删除,包括once的订阅者
for (var i = 0; i <= fns.length; i++) {
if (fns[i] === fn || fns.fn === fn) {
fns.splice(i, 1);
break;
}
}
return this;
},
//创建执行后立即销毁的订阅者
once(event, fn) {
function once() {
this.remove(event, once);
fn.apply(this, arguments);
}
//存储当前fn副本用于删除时的查找
once.fn = fn;
this.on(event, once);
return this;
},
};
remove
删除事件先获取fns
对应主题的函数列表进行一些判断,如果没指定删除列表中的哪个函数(函数引用)就默认把对应整个列表给删除,如果有传fn就在循环中和对应的函数进行引用的判断fns.fn === fn
是给once函数删除的时候使用的
once
这里给传入的订阅者包装成一个闭包函数,把订阅者fn
放在订阅者once
函数属性下,当对应订阅者执行的时候先执行这个闭包函数删除掉自身后再去执行挂在once
下的订阅者fn
,做到用完即删。
因为如果想使用remove
方法删除once
订阅者的话和删除普通订阅者不一样,单凭传入的fn(fns[i] === fn
)是删除不掉once
订阅者的(因为传入的fn
函数和once
包装函数引用不相等),需要用到包装函数下的fn
属性引用(fns.fn === fn
)去识别订阅者才能进行删除。
EventEmitter完整源码:
https://github.com/booms21/Ev...
总结
以上就是发布订阅模式从实现到具体讲解的所有内容了。无论做什么事都是需要一些套路, 设计模式就是前人总结出来的编程套路,而学习这些套路就是进阶高级的必经之路,有了套路不仅避免了一些开发中的问题,还节省了许多浪费在思考造轮子的时间,事半功倍。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。