2014-05-07 105 views
8

在香草js中做事件代理的最佳方式(最快/最合適)時尚是什麼?香草JavaScript事件代表

例如,如果我有這個jQuery中:

$('#main').on('click', '.focused', function(){ 
    settingsPanel(); 
}); 

如何翻譯,爲香草JS?也許.addEventListener()

我能想到這樣做的方式是:

document.getElementById('main').addEventListener('click', dothis); 
function dothis(){ 
    // now in jQuery 
    $(this).children().each(function(){ 
     if($(this).is('.focused') settingsPanel(); 
    }); 
} 

但似乎效率不高,特別是如果#main有許多兒童。

這是做到這一點的正確方法嗎?

document.getElementById('main').addEventListener('click', doThis); 
function doThis(event){ 
    if($(event.target).is('.focused') || $(event.target).parents().is('.focused') settingsPanel(); 
} 
+4

從'event.target'開始,查看它是否與選擇器匹配,如果是,則調用該函數。然後遍歷它的祖先,並且一直到這個'this'元素。你可以使用'node.matchesSelector()'來做測試,儘管在某些瀏覽器中它是用一個特定於瀏覽器的標誌來實現的,所以你需要將它包裝在一個函數中,或者把標記方法放在Element上。 prototype.matchesSelector' –

+1

建議你發佈這個答案。 –

+0

@EvanTrimboli:我不喜歡發佈答案。我會讓其他人得到代表。如果你願意,請隨意。 –

回答

9

I've come up with a simple solution這似乎運作得相當好(舊版IE的支持,儘管)。在這裏,我們延長EventTarget的原型提供了delegateEventListener方法使用以下語法其中工程:

EventTarget.delegateEventListener(string event, string toFind, function fn) 

我創建了一個相當複雜的fiddle證明它在行動,我們委派的所有事件綠色元素。停止傳播繼續工作,你可以訪問應該是event.currentTargetthis(與jQuery一樣)。

這裏是全解:

(function(document, EventTarget) { 
    var elementProto = window.Element.prototype, 
     matchesFn = elementProto.matches; 

    /* Check various vendor-prefixed versions of Element.matches */ 
    if(!matchesFn) { 
    ['webkit', 'ms', 'moz'].some(function(prefix) { 
     var prefixedFn = prefix + 'MatchesSelector'; 
     if(elementProto.hasOwnProperty(prefixedFn)) { 
     matchesFn = elementProto[prefixedFn]; 
     return true; 
     } 
    }); 
    } 

    /* Traverse DOM from event target up to parent, searching for selector */ 
    function passedThrough(event, selector, stopAt) { 
    var currentNode = event.target; 

    while(true) { 
     if(matchesFn.call(currentNode, selector)) { 
     return currentNode; 
     } 
     else if(currentNode != stopAt && currentNode != document.body) { 
     currentNode = currentNode.parentNode; 
     } 
     else { 
     return false; 
     } 
    } 
    } 

    /* Extend the EventTarget prototype to add a delegateEventListener() event */ 
    EventTarget.prototype.delegateEventListener = function(eName, toFind, fn) { 
    this.addEventListener(eName, function(event) { 
     var found = passedThrough(event, toFind, event.currentTarget); 

     if(found) { 
     // Execute the callback with the context set to the found element 
     // jQuery goes way further, it even has it's own event object 
     fn.call(found, event); 
     } 
    }); 
    }; 

}(window.document, window.EventTarget || window.Element)); 
+0

Thx,偉大的解決方案! – jaydoubleyou

+0

你應該首先檢查標準的前綴版本。 – gilly3

+0

@ gilly3我也應該計算一次,而不是每次。我會更新 –

0

我有一個類似的解決方案來實現事件委託。 它利用陣列功能slice,reverse,filterforEach

  • slice將NodeList從查詢轉換爲數組,必須在允許顛倒列表之前完成該數組。
  • reverse反轉陣列(使得最終traversion開始儘量靠近事件目標成爲可能。
  • filter檢查哪些元素包含event.target
  • forEach要求從只要經過濾的結果中的每個元件所提供的處理程序作爲處理不返回false

該函數返回創建的委託功能,這使得它可以以後刪除監聽器。 注意,本地event.stopPropagation()確實ñ不要停止穿越validElements,因爲冒泡階段已經遍歷到委託單元。

function delegateEventListener(element, eventType, childSelector, handler) { 
    function delegate(event){ 
     var bubble; 
     var validElements=[].slice.call(this.querySelectorAll(childSelector)).reverse().filter(function(matchedElement){ 
      return matchedElement.contains(event.target); 
     }); 
     validElements.forEach(function(validElement){ 
      if(bubble===undefined||bubble!==false)bubble=handler.call(validElement,event); 
     }); 
    } 
    element.addEventListener(eventType,delegate); 
    return delegate; 
} 

儘管不推薦延長天然原型,該功能可以(以IE或Node)被添加到原型EventTarget。這樣做時,請在功能中將element替換爲this,並刪除相應的參數(EventTarget.prototype.delegateEventListener = function(eventType, childSelector, handler){...})。