源码学习-发布订阅模式之Emitter

126 阅读3分钟

    最近在学习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文件

image.png

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源码的学习,我理解的也不是特别透彻,有错误纰漏之处请指正。