理解 JavaScript EventEmitter(3)

addListener(type, fn, context) { return _addListener.call(this, type, fn, context); } on(type, fn, context) { return this.addListener(type, fn, context); } once(type, fn, context) { return _addListener.call(this, type, fn, context, true); }

addListener 需要用 call 来改变 this 指向,指到了类的实例。once 则多传了一个标志位 true 来标志它只需要执行一次。这里你会看到我在 addListener 并没有传 false 作为标志位,主要是因为我懒,但并不会影响到程序的逻辑。因为前面的 fn.once = !!once 已经能很好的处理不传值的情况。没传值 !!once 为 false。

接下来讲 emit

emit(type, ...rest) { if (isNullOrUndefined(type)) { throw new Error('emit must receive at lease one argument'); } const events = this._events[type]; if (isNullOrUndefined(events)) return false; if (typeof events === 'function') { events.call(events.context || null, rest); if (events.once) { this.removeListener(type, events); } } else if (isArray(events)) { events.map(e => { e.call(e.context || null, rest); if (e.once) { this.removeListener(type, e); } }); } return true; }

事件触发需要指定具体的 type 否则直接抛出错误。这个很容易理解,你都没有指定名称,我怎么知道该去执行谁的事件。if (isNullOrUndefined(events)) return false,如果 type 对应的方法是 undefined 或者 null ,直接返回 false 。因为压根没有对应 type 的方法可以执行。而 emit 需要知道是否被成功触发。

接着判断 evnts 是不是一个方法,如果是, events.call(events.context || null, rest) 执行该方法,如果指定了执行主体,用 call 改变 this 的指向指向 events.context 主体,否则指向 null ,全局环境。对于浏览器环境来说就是 window。差点忘了 rest ,rest 是方法执行时的其他参数变量,可以不传,也可以为一个或多个。执行结束后判断 events.once ,如果为 true ,就用 removeListener 移除该监听事件。

如果 evnts 是数组,逻辑一样,只是需要遍历数组去执行所有的监听方法。

成功执行结束后返回 true 。

removeListener(type, fn) { if (isNullOrUndefined(this._events)) return this; // if type is undefined or null, nothing to do, just return this if (isNullOrUndefined(type)) return this; if (typeof fn !== 'function') { throw new Error('fn must be a function'); } const events = this._events[type]; if (typeof events === 'function') { events === fn && delete this._events[type]; } else { const findIndex = events.findIndex(e => e === fn); if (findIndex === -1) return this; // match the first one, shift faster than splice if (findIndex === 0) { events.shift(); } else { events.splice(findIndex, 1); } // just left one listener, change Array to Function if (events.length === 1) { this._events[type] = events[0]; } } return this; }

removeListener 接收一个事件名称 type 和一个将要被移除的方法 fn 。if (isNullOrUndefined(this._events)) return this 这里表示如果 EventEmitter 实例本身的 _events 为 null 或者 undefined 的话,没有任何事件监听,直接返回 this 。

if (isNullOrUndefined(type)) return this 如果没有提供事件名称,也直接返回 this 。

if (typeof fn !== 'function') { throw new Error('fn must be a function'); }

fn 如果不是一个方法,直接抛出错误,很好理解。

接着判断 type 对应的 events 是不是一个方法,是,并且 events === fn 说明 type 对应的方法有且仅有一个,等于我们指定要删除的方法。这个时候 delete this._events[type] 直接删除掉 this._events 对象里 type 即可。

所有的 type 对应的方法都被移除后。想一想 this._events[type] = undefined 和 delete this._events[type] 会有什么不同?

差异是很大的,this._events[type] = undefined 仅仅是将 this._events 对象里的 type 属性赋值为 undefined ,type 这一属性依然占用内存空间,但其实已经没什么用了。如果这样的 type 一多,有可能造成内存泄漏。delete this._events[type] 则直接删除,不占内存空间。前者也是 Node.js 事件模块和 eventemitter3 早期实现的做法。

如果 events 是数组,这里我没有用 isArray 进行判断,而是直接用一个 else ,原因是 this._events[type] 的输入限制在 on 或者 once 中,而它们已经限制了 this._events[type] 只能是方法组成的数组或者是一个方法,最多加上不小心或者人为赋成 undefined 或 null 的情况,但这个情况我们也在前面判断过了。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/pxgyf.html