前言 最近开始想写这个系列文章,一方面是自己刚开始学编程的时候,也花了比较多的时间去学习各种框架的API,但却不了解内部的运行原理,即使了解也是通过各种文章和博客泛泛的了解一下。但从后面的经历来看,想快速的掌握一个框架,尝试自己实现一个demo版,会有奇效。希望结合自己的描述,能让新人少走一些弯路。另一方面,一些同学对于 “看源码” 有着一点崇拜但抗拒的情绪。虽然大家都说可以从 0.x 版本看起,可以减少阅读成本。但其实对于一些同学门槛还是高了一点,这个系列期望能用更精简和可读的代码介绍各个轮子的原理。
接口设计
这期我们要实现的轮子是一个发布订阅的事件库,对标Events。我写代码一般习惯写之前先想好输入和输出,以及写好的代码会怎么用。发布订阅这种模型里面,两个核心的API on 和 emit ,通常用法如下:
var ee = new EventEmitter();
ee.on('message', function (text) {
console.log(text);
});
ee.emit('message', 'hello world 1');
订阅一类消息和触发它。除了这两个核心API,我们还会实现一些周边的便捷实用的API
- addEventListener(event, listener) 为指定的事件注册一个监听器
- emit(event) 触发指定事件
- on(event, listener) 和addEventListener一致,只是别名
- removeListener(event, listener): 移除指定事件的监听器
- removeAllListeners([event]):移除指定事件的所有监听回调
- once(event, listener): 和on类似,但只触发一次
第一版实现
有了接口设计和使用方法,可以开始造轮子了。先初始化一下代码
class EventEmitter {
on(event, listener) {
}
emit(event, params) {
}
}
export default EventEmitter
先写两个核心的API。我们要监听事件和触发事件,总要有个地方存吧,JS里面能存数据的结构无非是数组和对象,而我们的事件是有名字的,那就先用对象。在监听事件的时候存进去,触发的事件调用一下
class EventEmitter {
listeners = {};
on(event, listener) {
this.listeners[event] = listener;
}
emit(event, params) {
this.listeners[event](params);
}
}
跑一下我们最开始提的使用方法,一行醒目的 hello world 打印了出来。一个最小化demo就已经完成了,总共9行代码
完善
不知道看完上面代码的同学有没有一种 “我又能行了” 的感觉😂。在这个基础上我们继续完善一下。在实际使用过程中,不管是DOM还是Node.js都支持同一个事件有多个监听器,比如这样
var ee = new EventEmitter();
ee.on('message', function (text) {
console.log('第一次监听')
console.log(text);
});
ee.on('message', function (text) {
console.log('第二次监听')
console.log(text);
});
ee.emit('message', 'hello world 1');
这个难不倒我们,上面只存了一个listener,支持存多个listener就好了。数据结构改一下,变成数组:
class EventEmitter {
listeners = {};
on(event, listener) {
if (this.listeners[event]) {
this.listeners[event].push(listener)
} else {
this.listeners[event] = [listener]
}
}
emit(event, params) {
if (!this.listeners[event]) return;
this.listeners[event].forEach((listener) => {
listener(params)
})
}
}
触发事件时也调整为了依次调用监听器。
支持一下 addEventListener 方法:
addEventListener(event, listener) {
this.on(event, listener);
}
有了上面的思路,清除所有监听器也是水到渠成。直接delete
removeAllListeners(event) {
if (!this.listeners[event]) return;
delete this.listeners[event];
}
清除特定的 listener
removeListeners(event, listener) {
if (!this.listeners[event]) return;
const index = this.listeners[event].indexOf(listener);
this.listeners[event].splice(index, 1)
}
只触发一次的once: 触发过后就解除监听
once(event, listener) {
let self = this
this.on(event, function () {
let args = Array.prototype.slice.call(arguments);
listener.apply(null, args);
self.removeListeners(event, listener)
})
}
完整代码:源码地址
class EventEmitter {
listeners = {};
on(event, listener) {
if (this.listeners[event]) {
this.listeners[event].push(listener)
} else {
this.listeners[event] = [listener]
}
}
emit(event, params?) {
if (!this.listeners[event]) return;
this.listeners[event].forEach((listener) => {
listener(params)
})
}
addEventListener(event, listener) {
this.on(event, listener);
}
removeAllListeners(event) {
if (!this.listeners[event]) return;
delete this.listeners[event];
}
removeListeners(event, listener) {
if (!this.listeners[event]) return;
const index = this.listeners[event].indexOf(listener);
this.listeners[event].splice(index, 1)
}
once(event, listener) {
let self = this
this.on(event, function () {
let args = Array.prototype.slice.call(arguments);
listener.apply(null, args);
self.removeListeners(event, listener)
})
}
}
export default EventEmitter
小结
一个学习使用的小demo到这里就完成了,生产使用的源码会复杂得多,包含了各种各样的边际情况和异常处理,打印出开发易于阅读的堆栈提示。但其中的核心代码都是类似的