Delegação detalhada de eventos JavaScript

Conceitos Básicos

Delegação de eventos, em termos leigos, é delegar a função de um elemento para responder a eventos (clique, tecla ...) para outro elemento;

De um modo geral, o evento de um elemento ou grupo de elementos é delegado ao seu pai ou elemento externo. O elemento externo está realmente vinculado ao evento. Quando o evento responder ao elemento que precisa ser vinculado, ele será Através do evento mecanismo de borbulhamento para acionar o evento de ligação de seu elemento externo e, em seguida, executar a função no elemento externo.

Por exemplo, se um colega de classe em um dormitório chega ao mesmo tempo, uma maneira é pegá-los um por um de maneira tola. Outra maneira é confiar esse assunto ao chefe do dormitório e deixar uma pessoa sair e levar todos os entrega expressa. Em seguida, distribua para cada aluno do dormitório um por um de acordo com o destinatário;

Aqui, a entrega expressa é um evento, cada aluno se refere ao elemento DOM que precisa responder ao evento e o chefe do dormitório que sai para receber a entrega expressa é o elemento agente, então é esse elemento que realmente liga o evento , que é distribuído de acordo com o destinatário O processo de entrega expressa é durante a execução do evento, é necessário determinar quais um ou vários dos elementos delegados devem ser correspondidos pelo evento de resposta atual.

Borbulhamento de evento

Conforme mencionado anteriormente, a implementação da delegação de evento no DOM usa o mecanismo de bolha de evento, então o que é bolha de evento?

Em document.addEventListener, podemos definir o modelo de evento: bolha de evento e captura de evento.Geralmente, o modelo de bolha de evento é usado;

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

Conforme mostrado na figura acima, o modelo de evento é dividido em três etapas:

  • Fase de captura: No modelo de bolha de eventos, a fase de captura não responderá a nenhum evento;
  • Estágio de destino: o estágio de destino refere-se à resposta do evento ao elemento mais baixo que dispara o evento;
  • Estágio de bolhas: o estágio de bolhas é quando a resposta do gatilho do evento vai da camada inferior de destino por camada para a camada mais externa (nó raiz). O proxy do evento deve usar o mecanismo de bolhas de eventos para responder aos eventos que precisam ser respondido pela camada interna. Vincular à camada externa; ### evento

Vantagens da delegação

  1. Reduza o consumo de memória

Imagine se tivermos uma lista com um grande número de itens da lista, precisamos responder a um evento quando o item da lista é clicado;

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

Se você vincular uma função a cada item da lista, um por um, consumirá muita memória e muito desempenho em termos de eficiência;

Portanto, uma abordagem melhor é vincular o evento click à sua camada pai, ou seja ul, e então na implementação do evento ir para as correspondências para determinar o elemento de destino;

Portanto, a delegação de eventos pode reduzir muito o consumo de memória e economizar eficiência.

  1. Eventos ligados dinamicamente

Por exemplo, existem apenas alguns itens de lista no exemplo acima e vinculamos eventos a cada item de lista;

Em muitos casos, precisamos adicionar ou remover dinamicamente os elementos do item da lista por meio de AJAX ou operações do usuário. Então, toda vez que mudamos, precisamos religar os eventos aos elementos recém-adicionados e desvincular os eventos aos elementos que estão prestes a serem excluídos ;

Se você usar a delegação de evento, não haverá tal problema, porque o evento está vinculado à camada pai e não tem nada a ver com o aumento ou diminuição do elemento de destino. A execução para o elemento de destino é correspondida no processo de realmente respondendo à execução da função de evento. ;

Portanto, o uso de eventos pode reduzir muito trabalho repetitivo no caso de eventos de vinculação dinâmica.

Delegação de eventos em jQuery

Acredito que muitas pessoas usaram a delegação de eventos em jQuery, e ela é implementada principalmente das seguintes maneiras:

  • $ .on : Uso básico: $ ('. parent'). on ('click', 'a', function () {console.log ('click event on tag a');}), é o .parent elemento O evento do elemento a abaixo é delegado a $ ('. pai'). Enquanto houver um evento de clique neste elemento, ele encontrará automaticamente o elemento a no elemento .parent e, em seguida, responderá ao evento ;
  • $ .delegate : Uso básico: $ ('. parent'). delegate ('a', 'click', function () {console.log ('click event on tag a');}), o mesmo que acima, e também Correspondendo a $ .delegate para excluir o evento do agente;
  • $ .live : Uso básico: $ ('a', $ ('. parent')). live ('click', function () {console.log ('click event on tag a');}), o mesmo que acima, mas se o elemento pai $ (. pai) não for passado, o evento será delegado a $ (documento) por padrão; (abolido)

Perceba a função

Realização básica

Por exemplo, temos um 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 perceber que o proxy de evento do elemento li em #list é delegado a seu elemento pai, que é #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); } });

No código acima, o elemento de destino é o elemento que é clicado especificamente sob o elemento #list e, em seguida, julgando alguns atributos do destino (como nodeName, id, etc.), um certo tipo de elemento #list li pode ser correspondido com mais precisão Acima

Use Element.matches para corresponder exatamente

Se você alterar o HTML para:

<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

Aqui, queremos delegar o evento click do elemento li (e sua classe é class-1) sob o elemento #list para #list;

Se precisarmos dos métodos acima if (target.nodeName.toLocaleLowerCase === 'li')para determinar a adição de uma determinação target.nodeName.className === 'class-1';

Mas se você imaginar que o CSS opta por fazer uma correspondência mais flexível, os julgamentos acima são demais e é difícil obter flexibilidade.Aqui você pode usar a API Element.matches para fazer a correspondência;

O método de uso básico da API Element.matches: Element.matches (selectorString), selectorString é uma regra do seletor como CSS. Por exemplo, neste exemplo, você pode usar target.matches ('li.class-1'), que irá retornar um valor booleano, se o elemento de destino for o rótulo li e sua classe for class-1, ele retornará verdadeiro, caso contrário, retornará falso;

Claro, ainda existem alguns problemas com sua compatibilidade e ele precisa de uma versão de navegador moderna do IE9 e superior;

Podemos usar o  Polyfill  para resolver problemas de compatibilidade:

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

Depois de adicionar Element.matches, podemos atender às nossas necessidades:

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

Wrapper de função

Ao lidar com mais cenários, podemos encapsular a função do agente de evento em uma função comum, para que possa ser reutilizada.

Combine o exemplo acima para implementar uma função eventDelegate, que aceita quatro parâmetros:

  • [String] Uma string de seletor é usada para filtrar o elemento pai que precisa implementar o proxy, ou seja, o evento precisa ser vinculado a ele;
  • [String] Uma string do seletor é usada para filtrar os descendentes do elemento do seletor que disparou o evento, que é o elemento que precisamos delegar ao evento;
  • [String] Um ou mais tipos de eventos separados por espaços e namespaces opcionais, como click ou keydown.click;
  • [Função] A função que precisa de resposta de evento do agente;

Existem vários pontos-chave aqui: • Pode haver vários elementos do agente pai, e os eventos precisam ser vinculados um a um; • Pode haver vários tipos de eventos vinculados, e os eventos precisam ser vinculados um a um; • Correspondência está sendo processado Problemas de compatibilidade precisam ser considerados entre os elementos que estão sendo proxy; • Ao executar a função vinculada, você precisa passar os parâmetros corretos e considerar esse 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); }); }); }

? Otimização

Quando o elemento que está sendo proxy não é o elemento de destino e o elemento apontado pelo seletor targetSelector não é event.target (o elemento apontado pela fase de destino do evento), então é necessário atravessar o parentNode da camada event.target por camada para corresponder ao targetSelector. Até o parentSelector.

tal como:

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

Ainda delegue o evento de li para #list, mas desta vez encontraremos que event.target aponta para li span, então precisamos percorrer os elementos externos camada por camada para corresponder, até chegarmos à função do evento de proxy, pode usar event.currentTarget Para obter a função do evento do agente;

Função completa:

`function eventDelegate (parentSelector, targetSelector, events, foo) {// A função que dispara a função de execução triFunction (e) {// Tratamento de compatibilidade 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;
}

}

// Se houver vários eventos, você precisa vincular todos os eventos um por um events.split ('.'). ForEach (function (evt) {// Se houver vários elementos pais, você também precisa vincular um por um Array.prototype.slice .call (document.querySelectorAll (parentSelector)). ForEach (function ($ p) {$ p.addEventListener (evt, triFunction);});});} `

Função de uso:

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

Depois de clicar, você pode ver os #list liobjetos do elemento do console ;

?limitação

Obviamente, a delegação de eventos também tem certas limitações;

Por exemplo, eventos como foco e desfoque não têm mecanismo de bolha de eventos, portanto, não podem ser delegados;

Para eventos como mousemove e mouseout, embora haja eventos borbulhando, eles só podem ser calculados e posicionados continuamente por local, o que consome alto desempenho e, portanto, não é adequado para delegação de eventos;

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

Acho que você gosta

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