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



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

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

我正在尝试弄清楚代码如何识别函数,特别是在使用 addListener/removeListener

这些函数都接受 (字符串 s, 函数 f) 的签名

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

我想我特别想知道这条线是如何

list[i] === listener

也就是说,比较两个相等的函数,在JS中工作

但我不明白的是他们如何识别哪个函数 调用 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()中使用的内容:

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 中,函数是一等公民。简单来说,这意味着您可以将函数分配给变量,并将函数作为参数传递给其他函数。

下面是使用 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只是迭代其内部列表,直到找到特定的函数对象。

最新更新