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