2016-02-26 55 views
1

我期待在源Node.js的事件發射Node.js的事件發射器的源代碼的說明

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

我試圖找出代碼是如何識別功能,特別是使用addListener時/ removeListener

這些功能都接受的(一個String函數F)的簽名

,但我不明白的是,他們如何識別功能的時候再去除moveListener被調用,因爲可能有多個函數充當相同事件的回調。

我想我特別想知道這行

list[i] === listener 

就是平等比較兩種功能,在JS

工作

回答

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

但是,這實際上並不是實際的'eventEmitter'代碼。這個問題的答案應該解釋實際的'eventEmitter'代碼的作用,而不是理論上removeListener函數的工作原理。 – jfriend00

+1

@ jfriend00這幾乎是'removeListener'代碼的作用。這可能比整個代碼轉儲 – JCOC611

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()被用於在工作片斷什麼:

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

+0

謝謝!我想知道在比較函數時「list [i] === listener」是如何工作的 - 看起來他們正在使用===來比較函數......但是在JS中它是如何工作的?我以爲JS中的對象/函數沒有任何類型的id? –

+0

比較兩個函數f1 === f2時,JS對每個函數做了一個toString()然後比較?由於多種原因,這似乎很糟糕。 –

+1

@AlexMills - 函數是Javascipt中的第一類對象。因此,在這種情況下,'==='只是比較兩個操作數是否在相同的函數中引用。在JavaScript中,你可以這樣做:'function myFunc(){...}; var fn = myFunc; if(fn === myFunc){...}'。這就是這一切。函數只是一個對象引用,就像JS中的其他對象引用一樣。 – jfriend00

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只是迭代其內部列表,直到找到特定的函數對象。