Delega di eventi JavaScript alla vaniglia

Qual è il modo migliore (più veloce / corretto) per fare la delega degli eventi in vanilla js?

Ad esempio se avessi questo in jQuery:

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

Come posso tradurlo in vanilla js? Forse con .addEventListener()

Il modo in cui posso pensare di farlo è:

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

Ma questo sembra inefficiente, specialmente se #main ha molti figli.

È questo il modo corretto di farlo allora?

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

Ho trovato una soluzione semplice che sembra funzionare piuttosto bene (nonostante il supporto di IE legacy). Qui estendiamo il prototipo di EventTarget per fornire un metodo delegateEventListener che funziona usando la seguente syntax:

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

Ho creato un violino abbastanza complesso per dimostrarlo in azione, dove deleghiamo tutti gli eventi per gli elementi verdi. L’interruzione della propagazione continua a funzionare ed è ansible accedere a ciò che dovrebbe essere event.currentTarget attraverso this (come con jQuery).

Ecco la soluzione per intero:

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

Ho una soluzione simile per ottenere la delega degli eventi. Usa la funzione Array slice , reverse , filter e forEach .

  • slice converte il NodeList dalla query in un array, che deve essere eseguito prima che sia consentito invertire l’elenco.
  • inverte inverte la matrice (facendo in modo che la traversata finale inizi il più vicino ansible alla destinazione dell’evento.
  • filter controlla quali elementi contengono event.target .
  • forEach chiama il gestore fornito per ciascun elemento dal risultato filtrato a condizione che il gestore non restituisca false .

La funzione restituisce la funzione delegato creata, che consente di rimuovere in seguito il listener. Si noti che il nativo event.stopPropagation() non interrompe il movimento attraverso validElements , poiché la fase di bubbling ha già attraversato fino all’elemento delegante.

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

Sebbene non sia consigliato estendere i prototipi nativi, questa funzione può essere aggiunta al prototipo di EventTarget (o Node in IE). Nel fare ciò, sostituire l’ element con this all’interno della funzione e rimuovere il parametro corrispondente ( EventTarget.prototype.delegateEventListener = function(eventType, childSelector, handler){...} ).