Delegación detallada de eventos de JavaScript

conceptos básicos

La delegación de eventos, en términos sencillos, es delegar la función de un elemento para responder a eventos (clic, teclado ...) a otro elemento;

En términos generales, el evento de un elemento o grupo de elementos se delega a su elemento principal o externo. El elemento externo está realmente vinculado al evento. Cuando el evento responde al elemento que debe vincularse, lo hará a través del evento. mecanismo de burbujeo para activar el evento vinculante de su elemento exterior y luego ejecutar la función en el elemento exterior.

Por ejemplo, si un compañero de clase en un dormitorio llega al mismo tiempo, una forma es atraparlos uno por uno de una manera tonta. Otra forma es confiar este asunto al director del dormitorio y dejar que una persona salga y se lleve todos los Entrega urgente Luego distribuir a cada estudiante de la residencia uno por uno según el destinatario;

Aquí, la entrega urgente es un evento, cada alumno se refiere al elemento DOM que necesita responder al evento, y el jefe de dormitorio que sale a recibir la entrega urgente es el elemento agente, por lo que es este elemento el que realmente une al evento. , que se distribuye según el destinatario. El proceso de entrega urgente es durante la ejecución del evento, es necesario determinar cuál o varios de los elementos delegados deben coincidir con el evento de respuesta actual.

Evento burbujeante

Como se mencionó anteriormente, la implementación de la delegación de eventos en el DOM utiliza el mecanismo de propagación de eventos, entonces, ¿qué es la propagación de eventos?

En document.addEventListener, podemos configurar el modelo de eventos: propagación de eventos y captura de eventos En general, se utiliza el modelo de propagación de eventos;

https://pic1.zhimg.com/80/v2-bf3b8dbab027713a2b21b9e8a5b7a6c4_1440w.jpg

Como se muestra en la figura anterior, el modelo de eventos se divide en tres etapas:

  • Fase de captura: en el modelo de burbujeo de eventos, la fase de captura no responderá a ningún evento;
  • Etapa objetivo: la etapa objetivo se refiere a la respuesta del evento al elemento más bajo que desencadena el evento;
  • Etapa de burbujeo: La etapa de burbujeo es cuando la respuesta de activación del evento irá desde la capa objetivo inferior por capa hasta la capa más externa (nodo raíz). El proxy del evento es utilizar el mecanismo de burbujeo de eventos para responder a los eventos que necesitan ser respondido por la capa interna. Enlazar a la capa externa; evento ###

Ventajas de la delegación

  1. Reducir el consumo de memoria

Imagínese si tenemos una lista con una gran cantidad de elementos de la lista en la lista, necesitamos responder a un evento cuando se hace clic en el elemento de la lista;

<ul id="list"> <li>item 1</li> <li>item 2</li> <li>item 3</li> ...... <li>item n</li> </ul> // ...... 代表中间还有未知数个 li

Si vincula una función a cada elemento de la lista uno por uno, consumirá mucha memoria y consumirá mucho rendimiento en términos de eficiencia;

Por lo tanto, un mejor enfoque es vincular el evento de clic a su capa principal, es decir ul, y luego, en la implementación del evento, ir a coincidencias para determinar el elemento de destino;

Por lo tanto, la delegación de eventos puede reducir mucho el consumo de memoria y ahorrar eficiencia.

  1. Eventos vinculados dinámicamente

Por ejemplo, solo hay unos pocos elementos de la lista en el ejemplo anterior, y vinculamos eventos a cada elemento de la lista;

En muchos casos, necesitamos agregar o eliminar dinámicamente elementos de la lista a través de AJAX u operaciones del usuario. Luego, cada vez que cambiamos, necesitamos volver a vincular eventos a los elementos recién agregados y desvincular eventos a los elementos que están a punto de ser eliminados. ;

Si utiliza la delegación de eventos, no hay tal problema, porque el evento está vinculado a la capa principal y no tiene nada que ver con el aumento o la disminución del elemento de destino. La ejecución del elemento de destino coincide en el proceso de respondiendo a la ejecución de la función de evento. ;

Por lo tanto, el uso de eventos puede reducir una gran cantidad de trabajo repetitivo en el caso de eventos vinculantes dinámicos.

Delegación de eventos en jQuery

Creo que mucha gente ha utilizado la delegación de eventos en jQuery, y se implementa principalmente de estas formas:

  • $ .on : Uso básico: $ ('. parent'). on ('click', 'a', function () {console.log ('click event on tag a');}), es el .parent elemento El evento del elemento a a continuación se delega a $ ('. parent'). Siempre que haya un evento de clic en este elemento, encontrará automáticamente el elemento a debajo del elemento .parent y luego responderá al evento ;
  • $ .delegate : Uso básico: $ ('. parent'). delegate ('a', 'click', function () {console.log ('click event on tag a');}), igual que arriba, y también Correspondiente $ .delegate para borrar el evento del agente;
  • $ .live : Uso básico: $ ('a', $ ('. parent')). live ('click', function () {console.log ('click event on tag a');}), igual que arriba, pero si el elemento padre $ (. parent) no se pasa, el evento se delegará a $ (documento) de forma predeterminada; (abolido)

Darse cuenta de la función

Realización básica

Por ejemplo, tenemos un fragmento HTML como este:

<ul id**=**"list"> <li>item 1</li> <li>item 2</li> <li>item 3</li> ...... <li>item n</li> </ul> // ...... 代表中间还有未知数个 li

Vamos a darnos cuenta de que el proxy de eventos del elemento li en #list se delega a su elemento padre, que es #list:

*// 给父层元素绑定事件* document.getElementById('list').addEventListener('click', **function** (e) { *// 兼容性处理* **var** event **=** e **||** window.event; **var** target **=** event.target **||** event.srcElement; *// 判断是否匹配目标元素* **if** (target.nodeName.toLocaleLowerCase **===** 'li') { console.log('the content is: ', target.innerHTML); } });

En el código anterior, el elemento objetivo es el elemento en el que se hace clic específicamente debajo del elemento #list, y luego, al juzgar algunos atributos del objetivo (como nodeName, id, etc.), un cierto tipo de elemento #list li puede coincidir con más precisión Arriba

Use Element.matches para que coincida exactamente

Si cambia el HTML a:

<ul id**=**"list"> <li className**=**"class-1">item 1</li> <li>item 2</li> <li className**=**"class-1">item 3</li> ...... <li>item n</li> </ul> // ...... 代表中间还有未知数个 li

Aquí, queremos delegar el evento click del elemento li (y su clase es class-1) bajo el elemento #list a #list;

Si necesitamos los métodos anteriores para if (target.nodeName.toLocaleLowerCase === 'li')determinar la adición de una determinación target.nodeName.className === 'class-1';

Pero si imagina que CSS opta por hacer coincidencias más flexibles, los juicios anteriores son demasiado y es difícil lograr flexibilidad. Aquí puede usar la API Element.matches para igualar;

El método de uso básico de la API Element.matches: Element.matches (selectorString), selectorString es una regla de selección como CSS. Por ejemplo, en este ejemplo, puede usar target.matches ('li.class-1'), que devuelve un valor booleano. Si el elemento de destino es la etiqueta li y su clase es clase 1, devolverá verdadero; de lo contrario, devolverá falso;

Por supuesto, todavía existen algunos problemas con su compatibilidad, y necesita una versión de navegador moderna de IE9 y superior;

Podemos usar  Polyfill  para resolver problemas de compatibilidad:

**if** (**!**Element.prototype.matches) { Element.prototype.matches **=**Element.prototype.matchesSelector **||**Element.prototype.mozMatchesSelector **||**Element.prototype.msMatchesSelector **||**Element.prototype.oMatchesSelector **||**Element.prototype.webkitMatchesSelector **||function**(s) { **var** matches **=** (**this**.document **||** **this**.ownerDocument).querySelectorAll(s), i **=** matches.length; **while** (**--**i **>=** 0 **&&** matches.item(i) **!==** **this**) {} **return** i **>** **-**1; }; }

Después de agregar Element.matches, podemos lograr nuestras necesidades:

**if** (**!**Element.prototype.matches) { Element.prototype.matches **=**Element.prototype.matchesSelector **||**Element.prototype.mozMatchesSelector **||**Element.prototype.msMatchesSelector **||**Element.prototype.oMatchesSelector **||**Element.prototype.webkitMatchesSelector **||function**(s) { **var** matches **=** (**this**.document **||** **this**.ownerDocument).querySelectorAll(s), i **=** matches.length; **while** (**--**i **>=** 0 **&&** matches.item(i) **!==** **this**) {} **return** i **>** **-**1; }; } document.getElementById('list').addEventListener('click', **function** (e) { *// 兼容性处理* **var** event **=** e **||** window.event; **var** target **=** event.target **||** event.srcElement; **if** (target.matches('li.class-1')) { console.log('the content is: ', target.innerHTML); } });

Envoltorio de función

Al tratar con más escenarios, podemos encapsular la función del agente de eventos en una función común, de modo que pueda reutilizarse.

Combine el ejemplo anterior para implementar una función eventDelegate, que acepta cuatro parámetros:

  • [Cadena] Una cadena de selección se usa para filtrar el elemento padre que necesita implementar el proxy, es decir, el evento debe estar vinculado a él;
  • [Cadena] Una cadena de selección se utiliza para filtrar los descendientes del elemento selector que desencadenó el evento, que es el elemento que necesitamos delegar al evento;
  • [Cadena] Uno o más tipos de eventos separados por espacios y espacios de nombres opcionales, como click o keydown.click;
  • [Función] La función que necesita la respuesta del agente al evento;

Aquí hay varios puntos clave: • Puede haber varios elementos del agente principal y los eventos deben vincularse uno por uno; • Puede haber varios tipos de eventos vinculados y los eventos deben vincularse uno por uno; • Coincidencia se está procesando Los problemas de compatibilidad deben tenerse en cuenta entre los elementos que se están transfiriendo: • Al ejecutar la función vinculada, debe pasar los parámetros correctos y considerar este problema;

**function** eventDelegate (parentSelector, targetSelector, events, foo) { *// 触发执行的函数* **function** triFunction (e) { *// 兼容性处理* **var** event **=** e **||** window.event; **var** target **=** event.target **||** event.srcElement; *// 处理 matches 的兼容性* **if** (**!**Element.prototype.matches) { Element.prototype.matches **=**Element.prototype.matchesSelector **||**Element.prototype.mozMatchesSelector **||**Element.prototype.msMatchesSelector **||**Element.prototype.oMatchesSelector **||**Element.prototype.webkitMatchesSelector **||function**(s) { **var** matches **=** (**this**.document **||** **this**.ownerDocument).querySelectorAll(s), i **=** matches.length; **while** (**--**i **>=** 0 **&&** matches.item(i) **!==** **this**) {} **return** i **>** **-**1; }; } *// 判断是否匹配到我们所需要的元素上* **if** (target.matches(targetSelector)) { *// 执行绑定的函数,注意 this* foo.call(target, Array.prototype.slice.call(arguments)); } } *// 如果有多个事件的话需要全部一一绑定事件* events.split('.').forEach(**function** (evt) { *// 多个父层元素的话也需要一一绑定* Array.prototype.slice.call(document.querySelectorAll(parentSelector)).forEach(**function** ($p) { $p.addEventListener(evt, triFunction); }); }); }

? Optimización

Cuando el elemento que se está utilizando como proxy no es el elemento de destino, y el elemento al que apunta el selector targetSelector no es event.target (el elemento al que apunta la fase de destino del evento), entonces es necesario atravesar el parentNode de la capa event.target por capa para que coincida con el targetSelector. Hasta el parentSelector.

tal como:

<ul id**=**"list"> <li><span>item 1</span></li> <li><span>item 2</span></li> </ul>

Aún delegamos el evento de li a #list, pero esta vez encontraremos que event.target apunta a li span, por lo que necesitamos atravesar los elementos externos capa por capa para que coincidan, hasta que alcancemos la función del evento proxy, puede usar event.currentTarget Para obtener la función del evento del agente;

Función completa:

`function eventDelegate (parentSelector, targetSelector, events, foo) {// La función que activa la función de ejecución triFunction (e) {// Manejo de compatibilidad var event = e || window.event;

// 获取到目标阶段指向的元素
var target = event.target || event.srcElement;

// 获取到代理事件的函数
var currentTarget = event.currentTarget;

// 处理 matches 的兼容性
if (!Element.prototype.matches) {
  Element.prototype.matches =
    Element.prototype.matchesSelector ||
    Element.prototype.mozMatchesSelector ||
    Element.prototype.msMatchesSelector ||
    Element.prototype.oMatchesSelector ||
    Element.prototype.webkitMatchesSelector ||
    function(s) {
      var matches = (this.document || this.ownerDocument).querySelectorAll(s),
        i = matches.length;
      while (--i >= 0 && matches.item(i) !== this) {}
      return i > -1;            
    };
}

// 遍历外层并且匹配
while (target !== currentTarget) {
  // 判断是否匹配到我们所需要的元素上
  if (target.matches(targetSelector)) {
    var sTarget = target;
    // 执行绑定的函数,注意 this
    foo.call(sTarget, Array.prototype.slice.call(arguments))
  }

  target = target.parentNode;
}

}

// Si hay varios eventos, debe vincular todos los eventos uno por uno events.split ('.'). ForEach (function (evt) {// Si hay varios elementos principales, también debe vincular uno por uno Array.prototype.slice .call (document.querySelectorAll (parentSelector)). ForEach (function ($ p) {$ p.addEventListener (evt, triFunction);});});} `

Función de uso:

eventDelegate('#list', 'li', 'click', **function** () { console.log(**this**); });

Después de hacer clic, puede ver los #list liobjetos del elemento de la consola ;

?limitación

Por supuesto, la delegación de eventos también tiene ciertas limitaciones;

Por ejemplo, los eventos como el enfoque y el desenfoque no tienen un mecanismo de propagación de eventos, por lo que no se pueden delegar;

Para eventos como mousemove y mouseout, aunque hay eventos burbujeando, solo se pueden calcular y posicionar continuamente por ubicación, lo que consume un alto rendimiento y, por lo tanto, no es adecuado para la delegación de eventos;

Enlace original: https://zhuanlan.zhihu.com/p/26536815

Supongo que te gusta

Origin blog.csdn.net/terrychinaz/article/details/114461554
Recomendado
Clasificación