对于前端开发,js的事件系统可以说是我们接触最多的特性之一,甚至很多人把他当作了js与生俱来的特性,把js看作一门基于事件驱动的语言。
但当我们追本朔源,我们会发现,这个看似高深的特性又是一个基于发布/订阅模式的经典实现。下面,我们就仿照nodejs中的这一模块,一步步实现一个基本的事件发布系统。
基本特性
nodejs中的eventEmitter包含以下几个最基本的方法:
1 |
|
其中,最为核心的部分就是emit和addListener方法。
原理
既然是基于发布/订阅模式,那么这套功能的原理说起来也就简单了。首先我们维护一个事件中心EventEmitter,其中保存了一个如下所示的object,用来保存所有事件对应的回调函数:
1 |
|
addListener/on方法的作用就是向该object中注册相应的回调函数。如 button.on('click', function(){ console.log('hello') })
方法就该对象中的click事件对应的数组添加了一个匿名回调函数。
当对应的事件被触发时,对应事件的回调函数数组就会依次被调用。
实现
大概理解了原理之后,我们来看看一个简化版的addListener方法的实现:
1 |
|
由于事件系统使用范围十分广泛,因此,此处做了许多性能上的优化。
有了订阅函数,接下来我们再看看另一个核心函数emit:
1 |
|
相比官方的实现,此处我们省略了很多优化点,只考虑当事件发生时,如何调用相应的回调函数/数组。事实上,官方的实现中,针对传入的参数列表长度,实现了多个emit方法emitOne, emitTwo, emitThree
。另一个值得注意的地方是,每次emit函数调用时,对应该拷贝一份eventType对应的回调数组,避免回调数组中的函数在调用过程中发生增加/删除的情况。
有了这两个函数,我们基本上就实现了一个简单的事件发布/订阅系统。不过还有一个函数值得我们推敲,那就是once:
1 |
|
使用once函数注册的回调函数在回调一次之后马上就被销毁,其中onceWrap函数将once注册的函数进行包装,保证了这一特性。它的实现值得我们推敲。
总结
到这里,我们的简化版nodejs事件发布/订阅系可以说是告一段落了。阅读NodeJS的源码使我受益匪浅,除了精巧的设计,其中对很多情况的处理和优化也值得我们推敲学习。
有兴趣的读者可以尝试自己造一下轮子,相信你会有意想不到的收获。