最近在学习js设计模式,学到观察者模式时,作者推荐了Emitter源码,非常精短,很适合巩固学习。如果你对vue中的eventBus有过了解,那Emitter则不会陌生。
我们先看文档中的使用方法及对应的api,然后再读源码.
1.官方文档-使用方法:install后通过require引入
var {EventEmitter} = require('fbemitter');
var emitter = new EventEmitter();
2. 官方文档-API
constructor()
通过new一个EventEmitter实现继承
var {EventEmitter} = require('fbemitter');
var emitter = new EventEmitter();
addListener(eventType, callback)
为eventType注册一个监听回调,返回一个token可用来移除监听
var token = emitter.addListener('change', (...args) => {
console.log(...args);
});
emitter.emit('change', 10); // 10 is logged
token.remove();
emitter.emit('change', 10); // nothing is logged
once(eventType, callback)
和addListener类似,区别在执行一次后移除
var token = emitter.once('change', (...args) => {
console.log(...args);
});
emitter.emit('change', 10); // 10 is logged
emitter.emit('change', 10); // nothing is logged
removeAllListeners(eventType)
移除所有的监听事件,eventType是可选的,不提供的话移除所有事件,提供的话之只移除对应的事件
var token = emitter.addListener('change', (...args) => {
console.log(...args);
});
emitter.removeAllListeners();
emitter.emit('change', 10); // nothing is logged
listeners(eventType)
以数组形式返回当前已注册的监听事件
emit(eventType, ...args)
emit绑定的事件名称和数据,对应的监听事件回调则会收到通知,和addEventListener配套使用
var token = emitter.addListener('change', (...args) => {
console.log(...args);
});
emitter.emit('change', 10); // 10 is logged
__emitToSubscription(subscription, eventType, ...args)
通过extend的方式在emit时为每一个回调添加自定义逻辑,像日志、错误边界等;如果你只是想记录每次emit发生了什么而非emit的回调函数,可以通过复写emit的方式实现而非 __emitToSubscription
class MyEventEmitter extends EventEmitter {
__emitToSubscription(subscription, eventType) {
var args = Array.prototype.slice.call(arguments, 2);
var start = Date.now();
subscription.listener.apply(subscription.context, args);
var time = Date.now() - start;
MyLoggingUtility.log('callback-time', {eventType, time});
}
}
官方gitgub上关于Emitter的介绍就这么多,下面我们来看源码,源码的结构如下,主要文件是index和src下的4个js文件
3.源码文件
下面的源码删除了大部分原有注释,详见Emitter
index.js
// 通过module.exports导出,对应上面require的使用方式
// var {EventEmitter} = require('fbemitter');
var fbemitter = {
EventEmitter: require('./lib/BaseEventEmitter'),
EmitterSubscription : require('./lib/EmitterSubscription')
};
module.exports = fbemitter;
BaseEventEmitter.js
// 引入EmitterSubscription、EventSubscriptionVendor
const EmitterSubscription = require('EmitterSubscription');
const EventSubscriptionVendor = require('EventSubscriptionVendor');
const invariant = require('invariant');
const emptyFunction = require('emptyFunction');
// 通过new EventSubscriptionVendor(),将方法绑定到this._subscriber上,像addSubscription、removeAllSubscriptions、removeSubscription、getSubscriptionsForType
// 着重分析emit的方法,其他的api方法都是调用EmitterSubscription或EventSubscriptionVendor,emit是自己写的
class BaseEventEmitter {
constructor() {
this._subscriber = new EventSubscriptionVendor();
this._currentSubscription = null;
}
// 和官方的api说明对比,除removeCurrentListener外,其他均一致,有可能是removeAllListeners()也可以添加eventType,移除某个监听事件
addListener(
eventType: string,
listener,
context: ?Object,
): EmitterSubscription {
return this._subscriber.addSubscription(
eventType,
new EmitterSubscription(this._subscriber, listener, context),
);
}
once(eventType: string, listener, context: ?Object): EmitterSubscription {
var emitter = this;
return this.addListener(eventType, function () {
emitter.removeCurrentListener();
listener.apply(context, arguments);
});
}
removeAllListeners(eventType: ?string) {
this._subscriber.removeAllSubscriptions(eventType);
}
removeCurrentListener() {
invariant(
!!this._currentSubscription,
'Not in an emitting cycle; there is no current subscription',
);
this._subscriber.removeSubscription(this._currentSubscription);
}
listeners(eventType: string): Array /* TODO: Array<EventSubscription> */ {
var subscriptions = this._subscriber.getSubscriptionsForType(eventType);
return subscriptions
? subscriptions
.filter(emptyFunction.thatReturnsTrue)
.map(function (subscription) {
return subscription.listener;
})
: [];
}
emit(eventType) {
// subscriptions是个数组,具体看EventSubscriptionVendor中的代码
var subscriptions = this._subscriber.getSubscriptionsForType(eventType);
if (subscriptions) {
// keys是索引组成的数组
var keys = Object.keys(subscriptions);
for (var ii = 0; ii < keys.length; ii++) {
// key是索引
var key = keys[ii];
// subscription是eventName对应的回调函数
var subscription = subscriptions[key];
// The subscription may have been removed during this event loop.
if (subscription) {
this._currentSubscription = subscription;
this.__emitToSubscription.apply(
this,
// ['回调函数function', '事件名称']
[subscription].concat(Array.prototype.slice.call(arguments)),
);
}
}
this._currentSubscription = null;
}
}
__emitToSubscription(subscription, eventType) {
var args = Array.prototype.slice.call(arguments, 2);
subscription.listener.apply(subscription.context, args);
}
}
module.exports = BaseEventEmitter;
EventSubscriptionVendor.js
'use strict';
const invariant = require('invariant');
class EventSubscriptionVendor {
constructor() {
this._subscriptionsForType = {};
this._currentSubscription = null;
}
addSubscription(
eventType: String,
subscription: EventSubscription,
): EventSubscription {
invariant(
subscription.subscriber === this,
'The subscriber of the subscription is incorrectly set.',
);
// 类似单例模式,有则不操作,没有则设为空数组
if (!this._subscriptionsForType[eventType]) {
this._subscriptionsForType[eventType] = [];
}
// key是发布某个事件的长度,表示唯一标识
var key = this._subscriptionsForType[eventType].length;
this._subscriptionsForType[eventType].push(subscription);
// subscription有3个参数,subscriber(this)、eventType(监听的事件名称)、key(监听事件的长度)
subscription.eventType = eventType;
subscription.key = key;
return subscription;
}
removeAllSubscriptions(eventType: ?String) {
// eventType非必填项,没传则全部移除,传了则移除对应的事件
if (eventType === undefined) {
this._subscriptionsForType = {};
} else {
delete this._subscriptionsForType[eventType];
}
}
removeSubscription(subscription: Object) {
var eventType = subscription.eventType;
var key = subscription.key;
var subscriptionsForType = this._subscriptionsForType[eventType];
if (subscriptionsForType) {
delete subscriptionsForType[key];
}
}
// 返回数组,某个eventType下的所有监听
getSubscriptionsForType(eventType: String): ?Array {
return this._subscriptionsForType[eventType];
}
}
module.exports = EventSubscriptionVendor;
EmitterSubscription.js
代码很简短就不做解释和注释了
'use strict';
/**
* EventSubscription represents a subscription to a particular event. It can
* remove its own subscription.
*/
class EventSubscription {
/**
* @param {EventSubscriptionVendor} subscriber the subscriber that controls
* this subscription.
*/
constructor(subscriber: EventSubscriptionVendor) {
this.subscriber = subscriber;
}
/**
* Removes this subscription from the subscriber that controls it.
*/
remove() {
if (this.subscriber) {
this.subscriber.removeSubscription(this);
this.subscriber = null;
}
}
}
module.exports = EventSubscription;
核心部分是EventSubscriptionVendor、BaseEventEmitter,有想进一步学习EventBus的可以参照
面试官:请手写一个EventBus,让我看看你的代码能力!
掘金小册:JavaScript 设计模式核⼼原理与应⽤实践
以上是Emitter源码的学习,我理解的也不是特别透彻,有错误纰漏之处请指正。