2018-02-20
JavaScript
EventEmitter
先看如下代码:
00// test.js 
01module.exports = test; 
02
03function test(my_event_bus){
04    console.log('监听事件 喵');
05    my_event_bus.on('喵', function(e){
06        console.log('喵1, 参数:', e);
07    }); 
08    
09    my_event_bus.on('喵', function(e){
10        console.log('喵2, 参数:', e);
11    }); 
12    
13    my_event_bus.addListener('喵', function(e){
14        console.log('喵3, 参数: ', e); 
15    });
16    
17    setTimeout(() => {
18        my_event_bus.emit('喵', '啦!'); 
19    
20        console.log('on 和 addListener 是不是一样的 ? ');
21        console.log(my_event_bus.on === my_event_bus.addListener); 
22    }, 1500);     
23}
按照下面的方式调用 test.js
00// native-test
01const { EventEmitter } = require('events'); 
02const test = require('./test');
03
04let my_event = new EventEmitter(); 
05
06test(my_event);
输出结果如下
00监听事件 喵
01喵1, 参数: 啦!
02喵2, 参数: 啦!
03喵3, 参数:  啦!
04on 和 addListener 是不是一样的 ?
05true

# 自己实现一个 EventEmitter

EventEmitter 实现了 on 来监听一个事件,实现了 emit 来触发事件,执行监听器。
自己实现一个,应该也不难,如下:
00// 实现一个 EventEmitter 
01class Emily {
02    constructor(){
03        // 用来保存事件以及事件监听器的对象 
04        this.bus = {}
05    }
06
07    on(evt_name, fn){
08        // 某事件的监听队列 
09        let listeners = this.bus[evt_name]; 
10
11        // 若这个监听队列不存在,则创建一个空队列
12        if (!listeners) this.bus[evt_name] = []; 
13
14        // 插入到队尾 
15        this.bus[evt_name].push(fn); 
16
17        // 链式调用 
18        return this; 
19    }
20
21    emit(evt_name, evt_param){
22        let listeners = this.bus[evt_name]; 
23
24        // 说明监听队列不存在。 不报错直接 return this; 
25        if (!listeners) return this; 
26
27        // 否则执行监听器
28        listeners.forEach(fn => {
29            fn(evt_param); 
30        }); 
31
32        return this; 
33    }
34}
35
36// 别名 
37Emily.prototype.addListener = Emily.prototype.on; 
38
39// 暴露 
40module.exports = Emily;
然后编写 emily-test.js
00const Emily = require('./Emily')
01const test = require('./test'); 
02
03let my_event_bus = new Emily(); 
04
05test(my_event_bus);
结果与一开始的一样,没什么问题。

# 意味深长

EventEmitter 其实是很简洁的东西,这种简洁体现在它对真实世界关于事件的抽象:监听注册、触发执行。
理解了事件,也就理解了 EventEmitter,但似乎单个 EventEmitter 类似乎没有什么用,其实最佳的用法是继承它,一个类只要基础了它,就拥有了基础的事件驱动能力 (监听、触发)。
很多地方都能见到 on 和 emit 的身影,前端有 Vue :
00let bus = new Vue(); 
01
02bus.$on('wow', e => console.log('wow', e)); 
03bus.$emit('wow', '喵'); 
04// => 
05// wow 喵
更不用说 node 里各种 IO 操作了,比如 http 里的 request 的 on 方法监听事件,socketio 里的各种监听、触发。
这种关于事件的抽象,我们可以选择自己实现一个类似 EventEmitter 的构造,也可以直接让我们的对象继承自 EventEmitter 使其拥有事件驱动能力。
下面的 Alice.js 是一个继承 EventEmitter 的例子:
00const events = require('events');
01const { EventEmitter } = events; 
02
03class Alice extends EventEmitter {
04    constructor(el){
05        // 调用父级构造器 
06        super(); 
07    }
08
09    sayHello(){
10        console.log('hello'); 
11    }
12}
13
14module.exports = Alice;
JS 里很多的库,你顺着他们的原型链找上去,不出意外都可以看到 EventEmitter 的魅影,它非常重要,除了上面提到的 on 和 emit 方法,它还有很多其他的方法,但只要能理解事件,这些方法都非常容易接受,在此不多说。

# 在浏览器上应用 EventEmitter 的哲学

DOM 可以触发很多事件,比如各种鼠标的事件,键盘的事件,但依然不够用,比如没有 DOM 被双击 这个事件,但是我们可以自己实现,我们可以给单个 DOM 绑定点击事件来实现双击的侦测,然后做相应的处理,但是这样要在多个 DOM 节点应用就有点麻烦,因此还需要进一步的封装。
以下,实现一个 DOM 的双击事件,使用方式如下:
00// 在下面实现 double: 让 dom 能触发双击事件的修饰函数
01let double = require('./double'); 
02let header = document.querySelector('#header'); 
03
04// 修饰 
05double(header); 
06
07console.log(header)
08
09header.addEventListener('double-click', function(e){
10    console.log('被双击', e); 
11});
以下是 double 的实现
00/**
01 * @description 让 dom 能触发双击事件的修饰函数
02 * @param { Element } dom 
03 */
04function double(dom){
05    let speed = 0; 
06
07    dom.addEventListener('click', function(){
08        speed = speed + 1;  
09
10        setTimeout(() => {
11            speed = speed - 1
12        }, 300); 
13    }); 
14    
15    dom.addEventListener('click', function(click_event){
16        if (speed >= 2){
17            // 触发双击 
18            console.log('! catch 双击,现在应该构造双击事件,并派发到 dom 去'); 
19            
20            // click_event 其实也是 MouseEvent 的实例 
21            let doubleClick = new MouseEvent('double-click'); 
22
23            // 派发 
24            dom.dispatchEvent(doubleClick); 
25        }
26    }); 
27}
addEventlistener 相当于 EventEmitter 的 on;dispatchEvent 相当于 emit,它们是 EventTarget 上的方法,实现了对事件驱动的抽象。
而 Evnet 对象,其实是 on 的时候的一种参数,它指明了事件发生的各种参数,比如该 event 实例应该派发到哪个事件名,或者鼠标单击的坐标等等。




回到顶部