小插曲
事件
- 建议大家看下官网中events事件的描述node中events事件
- 发布订阅模式定义了一种一对多的依赖关系
- 在Node中EventEmitter开放on(事件名,回调函数)用于订阅事件
- emit(事件名)用于发布事件,可能对应多个订阅事件,让订阅事件依次执行
不明白?没关系。举个最简单例子,女人失恋了会哭,还会找新男朋友,在这里哭和找男朋友相当于订阅女人失恋的回调,什么时候执行呢?当发布女人失恋这件事的时候,说的这么抽象,直接来一段代码吧
- 是不是很简单,只有发布这个事件时候,被订阅的事件才会依次执行,形成一对多的依赖关系。接下来直接写源码实现
思路构建
- 我们想构造一个类似这样的对象 {"失恋":[cry,findBoy]},当事件发布的时候,让数组中对应的函数依次执行,就实现了这样的效果
- 先讲个小知识点 {}和Object.create(null)区别。 {}有作用链,通过Object.create(null)创造的空对象没有作用链,给大家演示下,其它就没啥区别。源码就是这样写(逼格高)
实现events模块
1、on和emit 两个核心方法
- 源码实现
// 声明EventEmitter事件发生器构造函数
function EventEmitter() {
this._events = Object.create(null);
}
//on 订阅方法实现 因为在实例上调用,所以写在原型上
EventEmitter.prototype.on = function(type,callback){
// 如果实例不存在则创建一个空对象,Object.create(null)没有链
if(!this._events) {
this._events = Object.create(null);
}
if(this._events[type]){ //如果失恋有对应的值,直接往数组push
this._events[type].push(callback)
}else { //第一次订阅,没有失恋,就声明{失恋:[cry]}
this._events[type] = [callback];
}
};
// emit方法实现
EventEmitter.prototype.emit = function(type){
if(this._events[type]){ //{失恋:[cry,eat]} 如果失恋对应有值,依次执行里面的方法
this._events[type].forEach(fn=>fn())
}
};
module.exports = EventEmitter
复制代码
- 十几行代码就实现核心功能,这么简单?对 就是这么简单,赶快来测试下吧
2、removeListener 取消订阅事件,失恋了不想哭了,所以我们提供个移除监听的方法
- 比较简单,直接上代码吧看的直接
// 移除订阅事件的方法
EventEmitter.prototype.removeListener = function(type,callback){
if(this._events[type]){
// 返回false就表示不要了,用filter实现去重
this._events[type] = this._events[type].filter(fn=>fn!==callback)
}
};
复制代码
- 测试下吧,失恋了不想哭了
- 完美实现,是不是很激动。
3、removeAllListeners移除全部的监听器,与removeListener相对应
// removeAllListeners 移除所有的监听者
EventEmitter.prototype.removeAllListeners = function(){
//简单粗暴,直接赋值空对象 {}
this._events = Object.create(null);
};
复制代码
- 测试下,失恋了既不想哭,也不想找对象,什么也不打印就对拉
4、扩展once方法 我们希望哭的事件 多次发布emit时候只执行一次,也就代表执行一次后需要将事件从对应关系中移除掉。
// once实现
EventEmitter.prototype.once = function(type,callback,flag){
// 先绑定 调用后再删除,运用了one函数 {失恋:one}
let one = (...args)=> {
callback(...args);
this.removeListener(type, one);
}
//自定义属性 因为实例中没有one属性
one.l = callback;
this.on(type,one)
};
// 移除订阅事件的方法
EventEmitter.prototype.removeListener = function(type,callback){
if(this._events[type]){
// 返回false就表示不要了,用filter实现去重
this._events[type] = this._events[type].filter(fn=>fn!==callback && fn.l!==callback)
}
};
复制代码
- 你可能会疑惑为什么声明一个wrap函数,设想下,不然你告诉我怎么先绑定一次,在移除。很多人可能都会这么写
- 错误例子 错误例子 错误例子(重要事情说三遍)
// - 错误例子 错误例子 错误例子(重要事情说三遍)
//你可能会这么写,但刚绑定就移除拉,体会这意思了吧
EventEmitter.prototype.once = function(type,callback){
//先绑定在移除
this.on(type,callback);
this.removeListener(type,callback)
};
复制代码
- 测试下吧,一步一个脚印
5、newListener方法。当cry添加到内部监听数组({失恋:[cry]})之前,会触发自身的'newListener'事件
- 没听懂?我们先来看官方的用法
简单说就是可以监控到订阅的事件类型,上源码看下如何实现
//on 订阅方法实现 因为在实例上调用,所以写在原型上
EventEmitter.prototype.on = function(type,callback){
// 如果实例不存在则创建一个空对象,Object.create(null)没有链
if(!this._events) {
this._events = Object.create(null);
}
if(type!=="newListener"){
if(this._events["newListener"]){
this._events["newListener"].forEach(fn=>fn(type))
}
}
if(this._events[type]){ //如果失恋有对应的值,直接往数组push
this._events[type].push(callback)
}else { //第一次订阅,没有失恋,就声明{失恋:[cry]}
this._events[type] = [callback];
}
};
复制代码
- 测试下吧
看到这里,基本方法都实现了。不常用就不解释拉。 如果大家想看所有源码方法解析,可以点进我github上参考
作者:言sir
链接:https://juejin.im/post/5b338118e51d4558a75e9921
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。