Node.js事件发射器源代码的解释

我正在查看Node.js事件发射器的源代码

https://github.com/nodejs/node/blob/master/lib/events.js

我想弄清楚代码如何识别一个函数,特别是当使用addListener / removeListener

这些函数都接受(String s,Function f)

但是我不明白的是,在调用removeListener时,如何确定删除哪个函数,因为可能有多个函数用作同一个事件的callback函数。

我想我特别想知道这条线

 list[i] === listener 

即比较两个平等的function,在JS中工作

但是我不明白的是,在调用removeListener时,如何确定删除哪个函数,因为可能有多个函数用作同一个事件的callback函数。

一个eventEmitter对象(或从它inheritance的任何东西)将存储它正在pipe理侦听器的所有事件名称的映射。 然后它可以为地图中的每个事件名称存储一组函数。 addListener()将函数添加到右侧列表中, removeListener()从右侧列表中删除匹配函数。 当你这样做时:

 obj.addListener("someEvent", someFunction); 

eventEmitter对象确保“someEvent”在它正在pipe理的事件名称的映射中,并且将someFunction添加到该特定事件名称的侦听器数组。 对于给定的事件名称可以有多个侦听器,所以只要有多个事件名称, eventEmitter使用一个数组,以便它可以存储该特定事件的所有侦听器函数。

addListener()removeListener()的代码通过一个优化来实现,这两个代码都很复杂,这使得代码更加难以实现。 如果给定事件有多个侦听器,则代码将在事件映射中存储一个侦听器函数数组。 但是,如果只有一个侦听器,那么它只是存储一个侦听器(没有数组)。 这意味着使用监听器列表的任何代码都必须首先检查它是单个监听器还是监听器数组。

removeListener()接受两个参数,一个事件types和一个函数。 目标是find一个以前注册的侦听器,用于注册该特定function的事件并将其删除。

发射器对象本身为每种types的事件存储一个函数数组。 所以,当调用removeListener(type, listener) ,调用者传入一个事件types和一个特定的函数。 eventEmitter代码将查找其数据,以查找传入的特定types事件的侦听器列表,然后在侦听器列表中search与侦听器传入的特定侦听器相匹配的侦听器列表。如果find,它将被删除。

下面是代码的注释副本,它应该解释removeListener()函数中每个代码块中发生了什么:

 // emits a 'removeListener' event iff the listener was removed EventEmitter.prototype.removeListener = function removeListener(type, listener) { var list, events, position, i; // make sure that listener was passed in and that it's a function if (typeof listener !== 'function') throw new TypeError('"listener" argument must be a function'); // get the map of events we have listeners for events = this._events; if (!events) return this; // get the list of functions for the specific event that was passed in list = events[type]; if (!list) return this; // handle some special cases when there is only one listener for an event if (list === listener || (list.listener && list.listener === listener)) { if (--this._eventsCount === 0) this._events = {}; else { delete events[type]; if (events.removeListener) this.emit('removeListener', type, listener); } } else if (typeof list !== 'function') { // when not a special case, we will have to find the right // function in the array so initialize our position variable position = -1; // search backward through the array of functions to find the // matching function for (i = list.length; i-- > 0;) { if (list[i] === listener || (list[i].listener && list[i].listener === listener)) { position = i; break; } } // if we didn't find it, nothing to do so just return if (position < 0) return this; // if the list has only one function in it, then just clear the list if (list.length === 1) { list[0] = undefined; if (--this._eventsCount === 0) { this._events = {}; return this; } else { delete events[type]; } } else { // remove that one function from the array spliceOne(list, position); } // send out an event if we actually removed a listener if (events.removeListener) this.emit('removeListener', type, listener); } return this; }; 

根据评论添加解释:

Javascript中的函数是第一类对象。 当代码使用=====来比较两个函数,或者比较一个variables和一个函数引用时,Javascript只是比较一下,看每个操作数是否指向同一个底层Javascript对象。 这里没有使用.toString() 。 这只是testing,看看他们是否指向相同的物理对象。

这里有几个例子:

 function myFunc() { console.log("hello"); } var a = myFunc; if (a === myFunc) { console.log("Yes, a does refer to myFunc"); } var b = a; if (b === a) { console.log("Yes, a and b refer to the same function"); } function myFunc2() { console.log("hello"); } a = myFunc; b = myFunc2; if (a !== b) { console.log("a and b do not refer to the same function"); } 

或者,更像是在工作代码片段中的addListener()removeListener()中使用的东西:

 function myFunc() { console.log("hello"); } var list = []; log("Items in list initially: " + list.length); list.push(myFunc); log("Items in list after adding: " + list.length); // search through list to find and remove any references to myFunc for (var i = list.length - 1; i >= 0; i--) { if (list[i] === myFunc) { list.splice(i, 1); } } log("Items in list after find/remove: " + list.length); // helper function to log output function log(x) { var div = document.createElement("div"); div.innerHTML = x; document.body.appendChild(div); } 

代码通过执行与以下类似的比较来查找函数:

 var events = [/*...*/]; function removeListener(type, fn){ for(var z = 0; z < events.length; z++){ if(events[z] === fn){ // fn event listener is equal to // the zth element of events, so // remove this element } } } 

addListenerremoveListener需要你传入一个函数。 该函数被存储在一个字典中,该字典将特定事件映射到为该事件注册的函数。

为了使removeListener正常工作,必须传入与addListener 相同的函数。 了解在JavaScript中,function是一stream的公民。 简而言之,这意味着您可以将一个函数分配给一个variables,并将函数作为parameter passing给其他函数。

这是一个使用addListener然后使用removeListener删除的例子,

 var cb = function() { console.log('event emitted'); } emitter.addListener('event', cb); // We can then remove the association by passing in cb as the // second argument to removeListener emitter.removeListener('event', cb); 

或者,我们可以使用命名函数来避免将函数分配给variables。

 emitter.addListener('event', function cb() { console.log('event emitted'); }); // Remove emitter.removeListener('event', cb); 

由于职能是一等公民,可以直接存储和比较。 使用这个, removeListener只是迭代其内部列表,直到find特定的函数对象。