¿Qué es la delegación de eventos DOM?

202

¿Alguien puede explicar la delegación de eventos en JavaScript y cómo es útil?

Xenón
fuente
2
Sería bueno si hubiera un enlace para hacer una fuente útil de información sobre esto. 6 horas después, este es el éxito de Google para "delegación de eventos dom". Tal vez este es un enlace útil? No estoy completamente seguro: w3.org/TR/DOM-Level-2-Events/events.html
Sean McMillan el
O tal vez esto: sitepoint.com/blogs/2008/07/23/…
Sean McMillan el
77
Este es uno popular. Incluso los chicos de Facebook se enlazan a esto para su página de reacción davidwalsh.name/event-delegate
Sorter
Vea este javascript.info/event-delegation le ayudará mucho
Suraj Jain

Respuestas:

330

La delegación de eventos DOM es un mecanismo para responder a los eventos ui a través de un solo padre común en lugar de cada hijo, a través de la magia del evento "burbujeando" (también conocido como propagación de eventos).

Cuando se desencadena un evento en un elemento, ocurre lo siguiente :

El evento se envía a su destino EventTargety se activa cualquier escucha de eventos que se encuentre allí. Los eventos burbujeantes activarán a los oyentes de eventos adicionales que se encuentren siguiendo la EventTargetcadena principal de la cadena ascendente , verificando si hay oyentes de eventos registrados en cada EventTarget sucesivo. Esta propagación ascendente continuará hasta e incluyendo el Document.

El burbujeo de eventos proporciona la base para la delegación de eventos en los navegadores. Ahora puede vincular un controlador de eventos a un solo elemento padre, y ese controlador se ejecutará cada vez que el evento ocurra en cualquiera de sus nodos secundarios (y en cualquiera de sus hijos a su vez). Esta es la delegación de eventos. Aquí hay un ejemplo de ello en la práctica:

<ul onclick="alert(event.type + '!')">
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
</ul>

Con ese ejemplo, si <li>hiciera clic en cualquiera de los nodos secundarios , vería una alerta de "click!", a pesar de que no hay un controlador de clic vinculado al <li>que hizo clic. Si nos unimos onclick="..."a cada <li>uno obtendrá el mismo efecto.

Entonces, ¿cuál es el beneficio?

Imagine que ahora necesita agregar dinámicamente nuevos <li>elementos a la lista anterior a través de la manipulación DOM:

var newLi = document.createElement('li');
newLi.innerHTML = 'Four';
myUL.appendChild(newLi);

Sin utilizar la delegación de eventos, tendría que "volver a vincular" el "onclick"controlador de eventos al nuevo <li>elemento para que actúe de la misma manera que sus hermanos. Con la delegación de eventos no necesitas hacer nada. Simplemente agregue el nuevo <li>a la lista y listo.

Esto es absolutamente fantástico para aplicaciones web con controladores de eventos vinculados a muchos elementos, donde los elementos nuevos se crean y / o eliminan dinámicamente en el DOM. Con la delegación de eventos, el número de enlaces de eventos se puede reducir drásticamente moviéndolos a un elemento principal común, y el código que crea dinámicamente nuevos elementos sobre la marcha se puede desacoplar de la lógica de enlace de sus controladores de eventos.

Otro beneficio de la delegación de eventos es que la huella de memoria total utilizada por los oyentes de eventos disminuye (ya que el número de enlaces de eventos disminuye). Puede que no haya mucha diferencia en las páginas pequeñas que se descargan con frecuencia (es decir, el usuario navega a diferentes páginas con frecuencia). Pero para aplicaciones de larga duración puede ser significativo. Hay algunas situaciones realmente difíciles de rastrear cuando los elementos eliminados del DOM aún reclaman memoria (es decir, pierden), y a menudo esta memoria perdida está vinculada a un enlace de evento. Con la delegación de eventos, puede destruir elementos secundarios sin riesgo de olvidarse de "desvincular" a sus oyentes de eventos (ya que el oyente está en el antepasado). Estos tipos de pérdidas de memoria se pueden contener (si no se eliminan, lo cual es muy difícil de hacer a veces. IE te estoy mirando).

Aquí hay algunos ejemplos de códigos concretos mejores de delegación de eventos:

Creciente Fresco
fuente
Tengo acceso prohibido al abrir su tercer enlace Delegación de eventos sin una biblioteca de JavaScript y +1 para su último enlace
bugwheels94
Hola, gracias por una gran explicación. Sin embargo, todavía estoy confundido acerca de cierto detalle: la forma en que entiendo el flujo de eventos del árbol DOM (como se puede ver en 3.1. Despacho de eventos y flujo de eventos DOM ) el objeto del evento se propaga hasta que alcanza el elemento objetivo y luego burbujea. ¿Cómo puede llegar a los elementos secundarios de un nodo si el padre de este nodo es el objetivo del evento en cuestión? por ejemplo, ¿cómo puede propagarse el evento a <li>cuando debería detenerse <ul>? Si mi pregunta aún no está clara o necesita un hilo separado, me complacería responder.
Aetos
@Aetos: > ¿Cómo puede llegar a elementos secundarios de un nodo si el padre de este nodo es el objetivo del evento en cuestión? No puede, según tengo entendido. El evento finaliza la fase 1 (captura) en el padre del objetivo, ingresa la fase 2 (objetivo) en el objetivo en sí, luego ingresa en la fase 3 (burbujeo) comenzando en el padre del objetivo. En ninguna parte llega a un hijo del objetivo.
Crescent Fresh
@Crescent Fresh, entonces, ¿cómo se aplica el evento en el nodo secundario si nunca lo alcanza?
Aetos
1
Muy buena respuesta. Gracias por explicar la delegación del evento con hechos relevantes. ¡Gracias!
Kshitij Tiwari
30

La delegación de eventos le permite evitar agregar oyentes de eventos a nodos específicos; en su lugar, el detector de eventos se agrega a un padre. Ese oyente de eventos analiza eventos burbujeados para encontrar una coincidencia en elementos secundarios.

Ejemplo de JavaScript:

Digamos que tenemos un elemento UL primario con varios elementos secundarios:

<ul id="parent-list">
<li id="post-1">Item 1</li>
<li id="post-2">Item 2</li>
<li id="post-3">Item 3</li>
<li id="post-4">Item 4</li>
<li id="post-5">Item 5</li>
<li id="post-6">Item 6</li>

Digamos también que algo debe suceder cuando se hace clic en cada elemento secundario. Puede agregar un detector de eventos separado a cada elemento LI individual, pero ¿qué sucede si los elementos LI se agregan y eliminan con frecuencia de la lista? Agregar y eliminar oyentes de eventos sería una pesadilla, especialmente si el código de adición y eliminación se encuentra en diferentes lugares dentro de su aplicación. La mejor solución es agregar un detector de eventos al elemento UL primario. Pero si agrega el detector de eventos al padre, ¿cómo sabrá en qué elemento se hizo clic?

Simple: cuando el evento aparece en el elemento UL, verifica la propiedad de destino del objeto del evento para obtener una referencia al nodo real en el que se hizo clic. Aquí hay un fragmento de JavaScript muy básico que ilustra la delegación de eventos:

// Get the element, add a click listener...
document.getElementById("parent-list").addEventListener("click", function(e) {
// e.target is the clicked element!
// If it was a list item
if(e.target && e.target.nodeName == "LI") {
    // List item found!  Output the ID!
    console.log("List item ", e.target.id.replace("post-"), " was clicked!");
       }
 });

Comience agregando un detector de eventos de clic al elemento principal. Cuando se activa el detector de eventos, verifique el elemento del evento para asegurarse de que sea el tipo de elemento al que reaccionar. Si es un elemento LI, boom: ¡tenemos lo que necesitamos! Si no es un elemento que queremos, el evento puede ser ignorado. Este ejemplo es bastante simple: UL y LI son una comparación directa. Probemos algo más difícil. Tengamos un DIV padre con muchos hijos, pero todo lo que nos importa es una etiqueta A con la clase classA CSS:

  // Get the parent DIV, add click listener...
  document.getElementById("myDiv").addEventListener("click",function(e) {
// e.target was the clicked element
if(e.target && e.target.nodeName == "A") {
    // Get the CSS classes
    var classes = e.target.className.split(" ");
    // Search for the CSS class!
    if(classes) {
        // For every CSS class the element has...
        for(var x = 0; x < classes.length; x++) {
            // If it has the CSS class we want...
            if(classes[x] == "classA") {
                // Bingo!
                console.log("Anchor element clicked!");
                // Now do something here....
            }
        }
    }

  }
});

http://davidwalsh.name/event-delegate

Osama AbuSitta
fuente
1
Ajuste sugerido: use e.classList.contains () en su lugar en el último ejemplo: developer.mozilla.org/en-US/docs/Web/API/Element/classList
nc.
7

La delegación de eventos dom es algo diferente de la definición de informática.

Se refiere al manejo de eventos burbujeantes de muchos elementos, como celdas de tabla, desde un objeto primario, como la tabla. Puede mantener el código más simple, especialmente al agregar o eliminar elementos, y ahorra algo de memoria.

Kennebec
fuente
6

La delegación es una técnica en la que un objeto expresa cierto comportamiento al exterior pero en realidad delega la responsabilidad de implementar ese comportamiento a un objeto asociado. Esto suena al principio muy similar al patrón proxy, pero tiene un propósito muy diferente. La delegación es un mecanismo de abstracción que centraliza el comportamiento del objeto (método).

En términos generales: use la delegación como alternativa a la herencia. La herencia es una buena estrategia, cuando existe una estrecha relación entre el objeto padre y el hijo, sin embargo, la herencia une los objetos muy de cerca. A menudo, la delegación es la forma más flexible de expresar una relación entre clases.

Este patrón también se conoce como "cadenas proxy". Varios otros patrones de diseño usan delegación: el Estado, la Estrategia y los Patrones de visitante dependen de ello.

Ewan Todd
fuente
Buena explicación. En el ejemplo de <ul> con varios <li> hijos, aparentemente los <li> son los que manejan la lógica de clic, pero no es así porque "delegan" esta lógica en el padre <ul>
Juanma Menéndez
6

El concepto de delegación

Si hay muchos elementos dentro de un elemento primario y desea controlar los eventos en ellos, no asocie controladores a cada elemento. En su lugar, vincule el controlador único a su padre y obtenga el hijo de event.target. Este sitio proporciona información útil sobre cómo implementar la delegación de eventos. http://javascript.info/tutorial/event-delegation

Joseph
fuente
6

La delegación de eventos está manejando un evento que burbujea usando un controlador de eventos en un elemento contenedor, pero solo activa el comportamiento del controlador de eventos si el evento ocurrió en un elemento dentro del contenedor que coincide con una condición dada. Esto puede simplificar el manejo de eventos en elementos dentro del contenedor.

Por ejemplo, suponga que desea manejar un clic en cualquier celda de una tabla grande. Usted podría escribir un bucle para conectar un controlador de clic a cada célula ... o se puede conectar un controlador de clic en la delegación de la tabla de eventos y utilizar para activar sólo para celdas de la tabla (y no los encabezados de tabla, o el espacio en blanco dentro de una remar alrededor de las celdas, etc.).

También es útil cuando va a agregar y eliminar elementos del contenedor, ya que no tiene que preocuparse por agregar y eliminar controladores de eventos en esos elementos; simplemente enganche el evento en el contenedor y maneje el evento cuando haga burbujas.

Aquí hay un ejemplo simple (es intencionalmente detallado para permitir una explicación en línea): Manejar un clic en cualquier tdelemento en una tabla de contenedor:

// Handle the event on the container
document.getElementById("container").addEventListener("click", function(event) {
    // Find out if the event targeted or bubbled through a `td` en route to this container element
    var element = event.target;
    var target;
    while (element && !target) {
        if (element.matches("td")) {
            // Found a `td` within the container!
            target = element;
        } else {
            // Not found
            if (element === this) {
                // We've reached the container, stop
                element = null;
            } else {
                // Go to the next parent in the ancestry
                element = element.parentNode;
            }
        }
    }
    if (target) {
        console.log("You clicked a td: " + target.textContent);
    } else {
        console.log("That wasn't a td in the container table");
    }
});
table {
    border-collapse: collapse;
    border: 1px solid #ddd;
}
th, td {
    padding: 4px;
    border: 1px solid #ddd;
    font-weight: normal;
}
th.rowheader {
    text-align: left;
}
td {
    cursor: pointer;
}
<table id="container">
    <thead>
        <tr>
            <th>Language</th>
            <th>1</th>
            <th>2</th>
            <th>3</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th class="rowheader">English</th>
            <td>one</td>
            <td>two</td>
            <td>three</td>
        </tr>
        <tr>
            <th class="rowheader">Español</th>
            <td>uno</td>
            <td>dos</td>
            <td>tres</td>
        </tr>
        <tr>
            <th class="rowheader">Italiano</th>
            <td>uno</td>
            <td>due</td>
            <td>tre</td>
        </tr>
    </tbody>
</table>

Antes de entrar en los detalles de eso, recordemos cómo funcionan los eventos DOM.

Los eventos DOM se envían desde el documento al elemento de destino (la fase de captura ), y luego burbujean desde el elemento de destino al documento (la fase de burbujeo ). Este gráfico en la antigua especificación de eventos DOM3 (ahora reemplazado, pero el gráfico aún es válido) lo muestra realmente bien:

ingrese la descripción de la imagen aquí

No todos los eventos burbujean, pero la mayoría sí, incluidos click .

Los comentarios en el ejemplo de código anterior describen cómo funciona. matchesverifica si un elemento coincide con un selector CSS, pero, por supuesto, puede verificar si algo coincide con sus criterios de otras maneras si no desea utilizar un selector CSS.

Ese código está escrito para llamar los pasos individuales de forma detallada, pero en navegadores vagamente modernos (y también en IE si usa un polyfill), puede usar closesty en containslugar del bucle:

var target = event.target.closest("td");
    console.log("You clicked a td: " + target.textContent);
} else {
    console.log("That wasn't a td in the container table");
}

Ejemplo en vivo:

closestcomprueba el elemento al que lo llama para ver si coincide con el selector CSS dado y, si lo hace, devuelve ese mismo elemento; si no, comprueba el elemento padre para ver si coincide y devuelve el padre si es así; si no, comprueba el padre del padre, etc. Entonces encuentra el elemento "más cercano" en la lista de antepasados ​​que coincide con el selector. Dado que puede pasar el elemento contenedor, el código anterior usa containspara verificar que si se encontró un elemento coincidente, está dentro del contenedor, ya que al enganchar el evento en el contenedor, has indicado que solo quieres manejar elementos dentro de ese contenedor .

Volviendo a nuestro ejemplo de tabla, eso significa que si tiene una tabla dentro de una celda de tabla, no coincidirá con la celda de tabla que contiene la tabla:

TJ Crowder
fuente
3

Básicamente se trata de cómo se asocia al elemento. .clickse aplica al DOM actual, mientras que .on(mediante delegación) seguirá siendo válido para los nuevos elementos agregados al DOM después de la asociación de eventos.

Cuál es mejor usar, diría que depende del caso.

Ejemplo:

<ul id="todo">
   <li>Do 1</li>
   <li>Do 2</li>
   <li>Do 3</li>
   <li>Do 4</li>
</ul>

.Haga clic en Evento:

$("li").click(function () {
   $(this).remove ();
});

Evento .on:

$("#todo").on("click", "li", function () {
   $(this).remove();
});

Tenga en cuenta que he separado el selector en el .on. Te explicaré por qué.

Supongamos que después de esta asociación, hagamos lo siguiente:

$("#todo").append("<li>Do 5</li>");

Ahí es donde notarás la diferencia.

Si el evento se asoció a través de .click, la tarea 5 no obedecerá al evento de clic y, por lo tanto, no se eliminará.

Si se asoció a través de .on, con el selector separado, obedecerá.

Lucas
fuente
2

Para comprender primero la delegación de eventos, necesitamos saber por qué y cuándo realmente necesitamos o queremos la delegación de eventos.

Puede haber muchos casos, pero analicemos dos grandes casos de uso para la delegación de eventos. 1. El primer caso es cuando tenemos un elemento con muchos elementos secundarios que nos interesan. En este caso, en lugar de agregar un controlador de eventos a todos estos elementos secundarios, simplemente lo agregamos al elemento principal y luego determinamos en qué elemento hijo se activó el evento.

2. El segundo caso de uso para la delegación de eventos es cuando queremos un controlador de eventos adjunto a un elemento que aún no está en el DOM cuando se carga nuestra página. Eso es, por supuesto, porque no podemos agregar un controlador de eventos a algo que no está en nuestra página, por lo que en caso de desaprobación que estamos codificando.

Suponga que tiene una lista de 0, 10 o 100 elementos en el DOM cuando carga su página, y hay más elementos esperando en su mano para agregar en la lista. Por lo tanto, no hay forma de adjuntar un controlador de eventos para los elementos futuros o esos elementos aún no se agregan en el DOM, y también puede haber muchos elementos, por lo que no sería útil tener un controlador de eventos adjunto a cada uno de ellos.

Delegación de eventos

Muy bien, entonces, para hablar sobre la delegación de eventos, el primer concepto del que realmente necesitamos hablar es el burbujeo de eventos.

Evento burbujeante: el evento burbujeante significa que cuando un evento se dispara o se activa en algún elemento DOM, por ejemplo haciendo clic en nuestro botón aquí en la imagen de abajo, entonces el mismo evento exacto también se activa en todos los elementos principales.

ingrese la descripción de la imagen aquí

El evento se dispara primero en el botón, pero luego también se disparará en todos los elementos principales, uno a la vez, por lo que también se disparará en el párrafo a la sección del elemento principal y, de hecho, en un árbol DOM. hasta el elemento HTML que es la raíz. Entonces decimos que el evento aparece dentro del árbol DOM, y por eso se llama burbujeo.

1 2 3 4 4

Elemento objetivo: El elemento en el que el evento se disparó primero se llama elemento objetivo, por lo que el elemento que causó el evento se llama elemento objetivo. En nuestro ejemplo anterior aquí es, por supuesto, el botón en el que se hizo clic. La parte importante es que este elemento objetivo se almacena como una propiedad en el objeto del evento. Esto significa que todos los elementos primarios en los que también se disparará el evento conocerán el elemento objetivo del evento, por lo que el evento se disparó por primera vez.

Eso nos lleva a la delegación de eventos. porque si el evento aparece en el árbol DOM y si sabemos dónde se activó el evento, simplemente podemos adjuntar un controlador de eventos a un elemento padre y esperar a que el evento se desarrolle, y podemos luego haga lo que pretendemos hacer con nuestro elemento objetivo. Esta técnica se llama delegación de eventos. En este ejemplo aquí, podríamos simplemente agregar el controlador de eventos al elemento principal.

Muy bien, de nuevo, la delegación de eventos es no configurar el controlador de eventos en el elemento original que nos interesa, sino adjuntarlo a un elemento padre y, básicamente, capturar el evento allí porque brota. Entonces podemos actuar sobre el elemento que nos interesa usar la propiedad del elemento de destino.

Ejemplo: Ahora supongamos que tenemos dos elementos de lista en nuestra página, después de agregar elementos en esas listas programáticamente, queremos eliminar uno o más elementos de ellos. Usando la técnica de delegación de eventos podemos lograr nuestro propósito fácilmente.

<div class="body">
    <div class="top">

    </div>
    <div class="bottom">
        <div class="other">
            <!-- other bottom elements -->
        </div>
        <div class="container clearfix">
            <div class="income">
                <h2 class="icome__title">Income</h2>
                <div class="income__list">
                    <!-- list items -->
                </div>
            </div>
            <div class="expenses">
                <h2 class="expenses__title">Expenses</h2>
                <div class="expenses__list">
                    <!-- list items -->
                </div>
            </div>
        </div>
    </div>
</div>

Agregar elementos en esa lista:

const DOMstrings={
        type:{
            income:'inc',
            expense:'exp'
        },
        incomeContainer:'.income__list',
        expenseContainer:'.expenses__list',
        container:'.container'
   }


var addListItem = function(obj, type){
        //create html string with the place holder
        var html, element;
        if(type===DOMstrings.type.income){
            element = DOMstrings.incomeContainer
            html = `<div class="item clearfix" id="inc-${obj.id}">
            <div class="item__description">${obj.descripiton}</div>
            <div class="right clearfix">
                <div class="item__value">${obj.value}</div>
                <div class="item__delete">
                    <button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button>
                </div>
            </div>
        </div>`
        }else if (type ===DOMstrings.type.expense){
            element=DOMstrings.expenseContainer;
            html = ` <div class="item clearfix" id="exp-${obj.id}">
            <div class="item__description">${obj.descripiton}</div>
            <div class="right clearfix">
                <div class="item__value">${obj.value}</div>
                <div class="item__percentage">21%</div>
                <div class="item__delete">
                    <button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button>
                </div>
            </div>
        </div>`
        }
        var htmlObject = document.createElement('div');
        htmlObject.innerHTML=html;
        document.querySelector(element).insertAdjacentElement('beforeend', htmlObject);
    }

Eliminar ítems:

var ctrlDeleteItem = function(event){
       // var itemId = event.target.parentNode.parentNode.parentNode.parentNode.id;
        var parent = event.target.parentNode;
        var splitId, type, ID;
        while(parent.id===""){
            parent = parent.parentNode
        }
        if(parent.id){
            splitId = parent.id.split('-');
            type = splitId[0];
            ID=parseInt(splitId[1]);
        }

        deleteItem(type, ID);
        deleteListItem(parent.id);
 }

 var deleteItem = function(type, id){
        var ids, index;
        ids = data.allItems[type].map(function(current){
            return current.id;
        });
        index = ids.indexOf(id);
        if(index>-1){
            data.allItems[type].splice(index,1);
        }
    }

  var deleteListItem = function(selectorID){
        var element = document.getElementById(selectorID);
        element.parentNode.removeChild(element);
    }
Señor
fuente
1

Un delegado en C # es similar a un puntero de función en C o C ++. El uso de un delegado permite al programador encapsular una referencia a un método dentro de un objeto delegado. El objeto delegado se puede pasar al código que puede llamar al método referenciado, sin tener que saber en el momento de la compilación qué método se invocará.

Vea este enlace -> http://www.akadia.com/services/dotnet_delegates_and_events.html

Bhaskar
fuente
55
No voy a rechazar esto, ya que probablemente fue una respuesta correcta a la pregunta original, pero la pregunta ahora es específicamente sobre la delegación de eventos DOM y Javascript
iandotkelly
1

La delegación de eventos utiliza dos características de los eventos de JavaScript que a menudo se pasan por alto: el burbujeo de eventos y el elemento de destino. Cuando un evento se activa en un elemento, por ejemplo, al hacer clic con el mouse en un botón, el mismo evento también se activa en todos los antepasados ​​de ese elemento . Este proceso se conoce como burbujeo de eventos; el evento emerge desde el elemento de origen hasta la parte superior del árbol DOM.

Imagine una tabla HTML con 10 columnas y 100 filas en las que desea que suceda algo cuando el usuario hace clic en una celda de la tabla. Por ejemplo, una vez tuve que hacer que cada celda de una tabla de ese tamaño fuera editable al hacer clic. Agregar controladores de eventos a cada una de las 1000 celdas sería un problema de rendimiento importante y, potencialmente, una fuente de pérdidas de memoria que bloquea el navegador. En cambio, utilizando la delegación de eventos, agregaría solo un controlador de eventos al elemento de la tabla, interceptaría el evento de clic y determinaría en qué celda se hizo clic.

Priti jha
fuente
0
Delegación de eventos

Adjunte un detector de eventos a un elemento primario que se active cuando se produzca un evento en un elemento secundario.

Propagación de eventos

Cuando un evento se mueve a través del DOM desde un elemento secundario a uno primario, eso se llama Propagación de eventos , porque el evento se propaga o se mueve a través del DOM.

En este ejemplo, un evento (onclick) de un botón se pasa al párrafo principal.

$(document).ready(function() {

    $(".spoiler span").hide();

    /* add event onclick on parent (.spoiler) and delegate its event to child (button) */
    $(".spoiler").on( "click", "button", function() {
    
        $(".spoiler button").hide();    
    
        $(".spoiler span").show();
    
    } );

});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

<p class="spoiler">
    <span>Hello World</span>
    <button>Click Me</button>
</p>

Codepen

antílope
fuente