Node.js事件发射器源代码的说明

时间:2016-02-26 20:05:09

标签: javascript node.js

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

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

我试图找出代码如何识别函数,特别是在使用addListener / removeListener

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

的签名

但我不明白的是他们如何识别调用removeListener时要删除的函数,因为可能有多个函数用作同一事件的回调。

我想我特别想知道这一行是怎么回事

list[i] === listener

即比较两个函数的相等性,可以在JS中使用

3 个答案:

答案 0 :(得分:4)

  

但我不明白的是他们如何识别哪个功能   调用removeListener时删除,因为可能有多个   用作同一事件的回调的函数。

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

obj.addListener("someEvent", someFunction);

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

addListener()removeListener()的代码由于优化而变得非常复杂,这些优化既实现又使得遵循代码变得更加困难。如果给定事件有多个侦听器,则代码会在事件映射中存储侦听器函数数组。但是,如果只有一个侦听器,那么它只存储一个侦听器(没有数组)。这意味着使用侦听器列表的任何代码都必须首先检查它是单个侦听器还是侦听器数组。

removeListener()有两个参数,一个事件类型和一个函数。目标是为该事件找到一个先前注册的监听器,该监听器注册该特定功能并将其删除。

发射器对象本身为每种类型的事件存储一系列函数。因此,当调用removeListener(type, listener)时,调用者传入事件类型和特定函数。 eventEmitter代码将查看其数据,以查找传入的特定事件类型的侦听器列表,然后搜索与传入的特定侦听器匹配的侦听器列表。如果找到,它将被删除。

这是一个带注释的代码副本,应该解释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中的函数是第一类对象。当代码使用=====来比较两个函数或将变量与函数引用进行比较时,Javascript只是比较以查看每个操作数是否引用相同的底层Javascript对象。这里没有使用.toString()。它只是测试它们是否引用相同的物理对象。

以下是几个例子:

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()中使用的内容:

&#13;
&#13;
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);
}
&#13;
&#13;
&#13;

答案 1 :(得分:1)

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

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
         }
    }
}

答案 2 :(得分:1)

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

要使removeListener有效,您必须传入传递给addListener相同函数。了解在JavaScript中,函数是一等公民。简单来说,这意味着您可以将函数赋值给变量,并将函数作为参数传递给其他函数。

以下是使用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);

或者,我们可以使用命名函数来避免将函数赋值给变量。

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

// Remove
emitter.removeListener('event', cb);

由于功能是一等公民,因此可以直接存储和比较它们。使用它,removeListener只是迭代其内部列表,直到找到特定的函数对象。