为了账号安全,请及时绑定邮箱和手机立即绑定

Make a simple custom EventEmitter

Thoughts

Recently I have been reading the book Async Javascript about JS asynchronicity and JS event is one of the useful solutions to the problem. To get a deeper understanding of how events work, I create a custom EventEmitter which constains most of the working functionalities of Node EventEmitter. The source code is no more than 60 lines.

General ideas

The general ideas is to have an object (this.handlers) to hold the mapping from event name(type: string) to its associated listeners/handlers(type: Array\<Function>). When each event is triggerd, walk through the associated listeners/handlers and execute them.

class Emitter {
    constructor(){
        /**
         * keep mapping information。
         * e.g. 
         *   {
         *      'event1': [fn1, fn2],
         *      'event2': [fn3, fn4, fn5]
         *   }
         */
        this.handlers = {};
    }
}
Some details about the methods

on - event binding

on(evt, handler) {
    this.handlers[evt] = this.handlers[evt] || [];
    let hdl = this.handlers[evt];
    hdl.push(handler);
    return this;
}

We don't check the duplicates when binding the handler for simplicity. That is to say, if you call on for the same function twice, then it will be called twice when the event is triggered. The method returns this to allow for method chaining。

off - event unbinding

removeListener(evt, handler) {
    this.handlers[evt] = this.handlers[evt] || [];
    let hdl = this.handlers[evt];
    let index = hdl.indexOf(handler);
    if(index >= 0) {
        hdl.splice(index, 1);
    }
    return this;
}

Note that here we compare the function reference with strict comparision when unbinding a function. Function in Javascript is compared by their reference, same as how objects comparision works.

function f1() {
    console.log('hi');
}

function f2() {
    console.log('hi');
}

let f3 = f1;
console.log(f1 === f2); //false
console.log(f1 === f3); //true

once - binding, but only can be triggerd once

once(evt, handler) {
    this.handlers[evt] = this.handlers[evt] || [];
    let hdl = this.handlers[evt];
    hdl.push(function f(...args){
        handler.apply(this, args);
        this.removeListener(evt, f);
    });
    return this;
}

It works similarly with on method. But we need to wrap the handler with another function such that we can remove the binding once the handler is executed to achieve triggered only once.

emit - trigger event

emit(evt, ...args) {
    this.handlers[evt] = this.handlers[evt] || [];
    let hdl = this.handlers[evt];
    hdl.forEach((it) => {
        it.apply(this, args);
    });
    return this;
}

When an event is triggered, find all its associated handlers(i.e. this.handlers[evt]) and execute them.

eventNames - get the list of registered events which has active(i.e. not-empty) handlers

eventNames() {
    return Object.keys(this.handlers).reduce((arr, evt) => {
        if(this.listenerCount(evt)) {
            arr.push(evt);
        }
        return arr;
    }, []);
}

Here we don't simply return all the keys of the this.handlers , as some events can be binded with some handlers and then remove them afterwards. In the case, the event name exists as a valid key in this.handlers but without active handlers. E.g.

let server = new Emitter();
let fn = function(){};
server.on('connection', fn);
server.removeListener('connection', fn);
server.handlers.connection; //[]

Therefore, we need to filter out the events with empty hanlders. Here we use Array.prototype.reduce to make the code a little bit cleaner. There are many situations where reduce can be useful, such as computing the sum of an array:

function sumWithForEach(arr) { // with forEach
    let sum = 0;
    arr.forEach(it => {
        sum += it;
    })
    return sum;
}

function sumWithReduce(arr) { // with reduce
    return arr.reduce((sum, current) => {
        return sum + current;
    })
}
Reference Notice
  • If you benefit from this Repo,Please「Star 」to Support.
  • If you want to follow the latest news/articles for the series of reading notes, Please 「Watch」to Subscribe.
点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消