什么是公布/订阅者模式?
置信大家在看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…
总结
以上就是公布订阅模式从实现到具体解说的所有内容了。无论做什么事都是须要一些套路, 设计模式就是前人总结进去的编程套路,而学习这些套路就是进阶高级的必经之路,有了套路不仅防止了一些开发中的问题,还节俭了许多节约在思考造轮子的工夫,事倍功半。
发表回复