了解Event Emitters事件发射器Typescript版

2019-04-173544次阅读事件发射器

事件发射器是一种模式,用于侦听命名事件,触发回调,然后使用值发出该事件。有时这被称为“发布/订阅"模式或监听器。它指的是同样的事情。

在JavaScript中,它的实现可能如下所示:

let n = 0;
const event = new EventEmitter();

event.subscribe("THUNDER_ON_THE_MOUNTAIN", value => (n = value));

event.emit("THUNDER_ON_THE_MOUNTAIN", 18);

// n: 18

event.emit("THUNDER_ON_THE_MOUNTAIN", 5);

// n: 5

在这个例子中,我们订阅了一个名为“THUNDER_ON_THE_MOUNTAIN”的事件,当发出该事件时,我们的回调value => (n = value)将被触发。要发出该事件,我们会调用emilt()。

这在处理异步代码时很有用,需要在与当前模块不在同一位置的地方更新一个值时会用到。


开始
让我们看看构建一个简单的事件发射器。我们将使用一个类,并在该类中跟踪事件:

class EventEmitter {
  public events: Events;
  constructor(events?: Events) {
    this.events = events || {};
  }
}


事件
我们将定义事件接口。我们将存储一个普通对象,其中每个键都是命名事件,其各自的值是回调函数的数组。

interface Events {
  [key: string]: Function[];
}

/**
{
  "event": [fn],
  "event_two": [fn]
}
*/

我们正在使用一个数组,因为每个事件可能有多个订阅者。想象一下在应用程序中调用element.addEventLister("click")的次数......可能不止一次。
 

订阅

现在我们需要处理订阅命名事件。在我们的简单示例中,该subscribe()函数有两个参数:一个名称和一个要触发的回调。

event.subscribe("named event", value => value);

让我们定义这个方法,这样我们的类就可以接受这两个参数。我们要做的就是将这些值附加到this.events上,我们在类内部跟踪这些事件。

class EventEmitter {
  public events: Events;
  constructor(events?: Events) {
    this.events = events || {};
  }

  public subscribe(name: string, cb: Function) {
    (this.events[name] || (this.events[name] = [])).push(cb);
  }
}


Emit发出
现在我们可以订阅活动了。接下来,我们需要在新事件发出时触发这些回调。当发生这种情况时,我们将使用我们存储的事件名(emit(“event”)和我们希望通过回调传递的任何值(emit(“event”,value))。老实说,我们不想对这些价值观作任何假设。我们只需在第一个参数之后将任何参数传递给回调。

class EventEmitter {
  public events: Events;
  constructor(events?: Events) {
    this.events = events || {};
  }

  public subscribe(name: string, cb: Function) {
    (this.events[name] || (this.events[name] = [])).push(cb);
  }

  public emit(name: string, ...args: any[]): void {
    (this.events[name] || []).forEach(fn => fn(...args));
  }
}

因为我们知道要发出哪个事件,所以可以使用javascript的对象括号语法(即this.events[name])来查找它。这给了我们存储的回调数组,因此我们可以遍历每个回调数组,并应用我们传递的所有值。
 

取消订阅

到目前为止,我们已经解决了主要问题。我们可以订阅一个事件并发出该事件。这才是最重要的。

现在我们需要能够取消订阅一个事件。我们在subscribe()函数中已经有了事件名和回调。因为我们可以有许多订阅任何一个事件的用户,所以我们希望单独删除回调:

subscribe(name: string, cb: Function) {
  (this.events[name] || (this.events[name] = [])).push(cb);

  return {
    unsubscribe: () =>
      this.events[name] && this.events[name].splice(this.events[name].indexOf(cb) >>> 0, 1)
  };
}

这将返回一个带有unsubscribe方法的对象。我们使用箭头函数(() =>)来获取传递给对象父级的参数的作用域。在这个函数中,我们将找到传递给父对象的回调索引并使用按位运算符(>>>)。按位运算符具有漫长而复杂的历史记录(您可以阅读所有内容)。在这里使用一个函数可以确保每次调用回调数组的splice()时总是得到一个实数,即使indexof()没有返回一个数字。

不管怎样,它对我们来说是可用的,我们可以这样使用它:

const subscription = event.subscribe("event", value => value);

subscription.unsubscribe();


代码集合

interface Events {
  [key: string]: Function[];
}

export class EventEmitter {
  public events: Events;
  constructor(events?: Events) {
    this.events = events || {};
  }

  public subscribe(name: string, cb: Function) {
    (this.events[name] || (this.events[name] = [])).push(cb);

    return {
      unsubscribe: () =>
        this.events[name] && this.events[name].splice(this.events[name].indexOf(cb) >>> 0, 1)
    };
  }

  public emit(name: string, ...args: any[]): void {
    (this.events[name] || []).forEach(fn => fn(...args));
  }
}

使用:

const events = new EventEmitter();

events.emit("authentication", false);

events.subscribe("authentication", isLoggedIn => {
  buttonEl.setAttribute("disabled", !isLogged);
});

events.subscribe("authentication", isLoggedIn => {
  window.location.replace(!isLoggedIn ? "/login" : "");
});

events.subscribe("authentication", isLoggedIn => {
  !isLoggedIn && cookies.remove("auth_token");
});

节选译自:https://css-tricks.com/understanding-event-emitters/

上一篇: JavaScript设计模式之发布订阅模式  下一篇: CSS Scroll Snap实现滚动捕捉及填充滚动容器空白区域  

了解Event Emitters事件发射器Typescript版相关文章