事件发射器是一种模式,用于侦听命名事件,触发回调,然后使用值发出该事件。有时这被称为“发布/订阅"模式或监听器。它指的是同样的事情。
在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");
});