Todos os eventos jquery devem ser vinculados a $ (documento)?

164

De onde isso vem

Quando aprendi o jQuery, normalmente anexava eventos como este:

$('.my-widget a').click(function() {
    $(this).toggleClass('active');
});

Depois de aprender mais sobre a velocidade do seletor e a delegação de eventos, li em vários lugares que "a delegação de eventos do jQuery tornará seu código mais rápido". Então comecei a escrever código como este:

$('.my-widget').on('click','a',function() {
    $(this).toggleClass('active');
});

Essa também foi a maneira recomendada de replicar o comportamento do evento .live () descontinuado. O que é importante para mim, pois muitos dos meus sites adicionam / removem dinamicamente widgets o tempo todo. Porém, o acima não se comporta exatamente como .live (), pois apenas os elementos adicionados ao contêiner já existente '.my-widget' receberão o comportamento. Se eu adicionar dinamicamente outro bloco de html após a execução desse código, esses elementos não vincularão os eventos a eles. Como isso:

setTimeout(function() {
    $('body').append('<div class="my-widget"><a>Click does nothing</a></div>');
}, 1000);


O que eu quero alcançar:

  1. o antigo comportamento de .live () // significa anexar eventos a elementos ainda não existentes
  2. os benefícios de .on ()
  3. desempenho mais rápido para vincular eventos
  4. Maneira simples de gerenciar eventos

Agora, anexo todos os eventos como este:

$(document).on('click.my-widget-namespace', '.my-widget a', function() {
    $(this).toggleClass('active');
});

O que parece cumprir todos os meus objetivos. (Sim, é mais lento no IE por algum motivo, não faz ideia do porquê?) É rápido porque apenas um único evento está vinculado a um elemento singular e o seletor secundário é avaliado apenas quando o evento ocorre (me corrija se isso estiver errado aqui). O espaço para nome é incrível, pois facilita a alternância do ouvinte de evento.

Minha Solução / Pergunta

Então, estou começando a pensar que os eventos do jQuery devem sempre estar vinculados a $ (documento).
Existe alguma razão para você não querer fazer isso?
Isso poderia ser considerado uma prática recomendada? Se não, por que?

Se você leu tudo isso, obrigado. Agradeço qualquer / todos os comentários / idéias.

Premissas:

  1. Usando jQuery que suporta .on()// pelo menos a versão 1.7
  2. Você deseja que o evento seja adicionado ao conteúdo adicionado dinamicamente

Leituras / Exemplos:

  1. http://24ways.org/2011/your-jquery-now-with-less-suck
  2. http://brandonaaron.net/blog/2010/03/4/event-delegation-with-jquery
  3. http://www.jasonbuckboyer.com/playground/speed/speed.html
  4. http://api.jquery.com/on/
bode
fonte

Respostas:

215

Não - você NÃO deve vincular todos os manipuladores de eventos delegados ao documentobjeto. Esse é provavelmente o cenário com pior desempenho que você pode criar.

Primeiro, a delegação de eventos nem sempre torna seu código mais rápido. Em alguns casos, é vantajoso e, em alguns casos, não. Você deve usar a delegação de eventos quando realmente precisar da delegação de eventos e quando se beneficiar dela. Caso contrário, você deve vincular manipuladores de eventos diretamente aos objetos em que o evento acontece, pois isso geralmente será mais eficiente.

Segundo, você NÃO deve vincular todos os eventos delegados no nível do documento. É exatamente por isso que .live()foi preterido, porque isso é muito ineficiente quando você tem muitos eventos vinculados dessa maneira. Para manipulação de eventos delegados, é MUITO mais eficiente vinculá-los ao pai mais próximo que não é dinâmico.

Terceiro, nem todos os eventos funcionam ou todos os problemas podem ser resolvidos com a delegação. Por exemplo, se você deseja interceptar eventos-chave em um controle de entrada e impedir que chaves inválidas sejam inseridas no controle de entrada, não é possível fazer isso com a manipulação de eventos delegados porque, quando o evento chegar ao manipulador delegado, ele já estará foi processado pelo controle de entrada e é tarde demais para influenciar esse comportamento.

Aqui estão os momentos em que a delegação de eventos é necessária ou vantajosa:

  • Quando os objetos nos quais você está capturando eventos são criados / removidos dinamicamente e você ainda deseja capturar eventos neles, sem precisar religar explicitamente os manipuladores de eventos toda vez que cria um novo.
  • Quando você tem muitos objetos que desejam exatamente o mesmo manipulador de eventos (onde lotes é pelo menos centenas). Nesse caso, pode ser mais eficiente no momento da instalação vincular um manipulador de eventos delegados em vez de centenas ou mais manipuladores diretos de eventos. Observe que o tratamento de eventos delegados é sempre menos eficiente em tempo de execução do que os manipuladores diretos de eventos.
  • Quando você está tentando capturar (em um nível mais alto no seu documento) eventos que ocorrem em qualquer elemento do documento.
  • Quando o seu design está usando explicitamente o evento bubbling e stopPropagation () para resolver algum problema ou recurso em sua página.

Para entender um pouco mais, é preciso entender como os manipuladores de eventos delegados do jQuery funcionam. Quando você chama algo assim:

$("#myParent").on('click', 'button.actionButton', myFn);

Ele instala um manipulador de eventos jQuery genérico no #myParentobjeto. Quando um evento de clique é associado a esse manipulador de eventos delegados, o jQuery precisa percorrer a lista de manipuladores de eventos delegados anexados a esse objeto e verificar se o elemento de origem do evento corresponde a algum dos seletores nos manipuladores de eventos delegados.

Como os seletores podem ser bastante envolvidos, isso significa que o jQuery precisa analisar cada seletor e compará-lo às características do destino do evento original para ver se ele corresponde a cada seletor. Esta não é uma operação barata. Não é grande coisa se houver apenas um deles, mas se você colocar todos os seus seletores no objeto de documento e houver centenas de seletores para comparar com cada evento com bolhas, isso poderá prejudicar seriamente o desempenho da manipulação de eventos.

Por esse motivo, você deseja configurar seus manipuladores de eventos delegados para que um manipulador de eventos delegados fique o mais próximo possível do objeto de destino. Isso significa que menos eventos serão exibidos em cada manipulador de eventos delegados, melhorando assim o desempenho. Colocar todos os eventos delegados no objeto de documento é o pior desempenho possível, porque todos os eventos com bolhas precisam passar por todos os manipuladores de eventos delegados e serem avaliados com relação a todos os possíveis seletores de eventos delegados. É exatamente por isso que .live()foi preterido, porque foi o que .live()aconteceu e provou ser muito ineficiente.


Portanto, para alcançar um desempenho otimizado:

  1. Use apenas a manipulação de eventos delegados quando realmente fornecer um recurso necessário ou aumentar o desempenho. Nem sempre use-o porque é fácil, porque quando você realmente não precisa. Na verdade, ele tem desempenho pior no momento da expedição do evento do que a ligação direta ao evento.
  2. Anexe os manipuladores de eventos delegados ao pai mais próximo da origem do evento possível. Se você estiver usando a manipulação de eventos delegados porque possui elementos dinâmicos para os quais deseja capturar eventos, selecione o pai mais próximo que não é dinâmico.
  3. Use seletores fáceis de avaliar para manipuladores de eventos delegados. Se você seguiu como a manipulação de eventos delegados funciona, você entenderá que um manipulador de eventos delegados deve ser comparado a muitos objetos várias vezes, para escolher um seletor o mais eficiente possível ou adicionar classes simples a seus objetos para que seletores mais simples possam ser usados. aumentar o desempenho da manipulação de eventos delegados.
jfriend00
fonte
2
Impressionante. Obrigado pela descrição rápida e detalhada. Isso faz sentido que o tempo de envio do evento aumente usando .on (), mas acho que ainda estou lutando para decidir entre o aumento do tempo de envio do evento e a equipe de processamento da página inicial. Eu geralmente sou para diminuir o tempo da página inicial. Por exemplo, se eu tiver 200 elementos em uma página (e mais à medida que os eventos acontecem), é cerca de 100 vezes mais caro no carregamento inicial (já que ele precisa adicionar 100 ouvintes de eventos) em vez de adicionar um ouvinte de evento singular no link do
Buck
Também queria dizer que você me convenceu - Não vincule todos os eventos a $ (documento). Associe-se ao pai mais próximo e sem alteração possível. Acho que ainda terei que decidir caso a caso quando a delegação de eventos for melhor do que vincular diretamente um evento a um elemento. E quando eu precisar de eventos vinculados a conteúdo dinâmico (que não tenham um pai que não mude), acho que continuarei fazendo o que a @Vega recomenda abaixo e simplesmente "vincular o (s) manipulador (es) depois que o conteúdo for inserido (o ) DOM ", usando o parâmetro de contexto do jQuery no meu seletor.
Buck
5
@ user1394692 - se você tiver muitos elementos quase idênticos, poderá fazer sentido usar manipuladores de eventos delegados para eles. Eu digo isso na minha resposta. Se eu tivesse uma tabela gigante de 500 linhas e tivesse o mesmo botão em todas as linhas de uma coluna específica, usaria um manipulador de eventos delegados na tabela para atender a todos os botões. Mas se eu tivesse 200 elementos e todos precisassem de um manipulador de eventos exclusivo, a instalação de 200 manipuladores de eventos delegados não é mais rápida do que a instalação de 200 manipuladores de eventos diretamente ligados, mas a manipulação de eventos delegados pode ser muito mais lenta na execução do evento.
jfriend00
Você é perfeito. Concordo plenamente! Entendo que essa é a pior coisa que posso fazer $(document).on('click', '[myCustomAttr]', function () { ... });?
Artem Fitiskin
O uso de pjax / turbolinks apresenta um problema interessante, pois todos os elementos são criados / removidos dinamicamente (além do carregamento da primeira página). Portanto, é melhor vincular todos os eventos uma vez ao document, ou vincular a seleções mais específicas page:loade desvincular esses eventos page:after-remove? Hmmm
Dom Christie
9

A delegação de eventos é uma técnica para gravar seus manipuladores antes que o elemento realmente exista no DOM. Este método tem suas próprias desvantagens e deve ser usado apenas se você tiver esses requisitos.

Quando você deve usar a delegação de eventos?

  1. Quando você liga um manipulador comum a mais elementos que precisam da mesma funcionalidade. (Ex: passe o cursor da linha da tabela)
    • No exemplo, se você tivesse que ligar todas as linhas usando ligação direta, acabaria criando n manipulador para n linhas nessa tabela. Usando o método de delegação, você pode acabar manipulando todos aqueles em um manipulador simples.
  2. Quando você adiciona conteúdo dinâmico com mais frequência no DOM (por exemplo: Adicionar / remover linhas de uma tabela)

Por que você não deve usar a delegação de eventos?

  1. A delegação de eventos é mais lenta quando comparada à associação direta do evento ao elemento.
    • Ele compara o seletor de alvo em cada bolha que atinge, a comparação será tão cara quanto complicada.
  2. Nenhum controle sobre o evento borbulhante até atingir o elemento ao qual está vinculado.

PS: Mesmo para conteúdos dinâmicos, você não precisa usar o método de delegação de eventos se for vincular o manipulador após a inserção do conteúdo no DOM. (Se o conteúdo dinâmico for adicionado com frequência, não será removido / adicionado novamente)

Selvakumar Arumugam
fonte
0

Gostaria de acrescentar algumas observações e contra-argumentos à resposta de jfriend00. (principalmente apenas minhas opiniões baseadas no meu instinto)

Não - você NÃO deve vincular todos os manipuladores de eventos delegados ao objeto de documento. Esse é provavelmente o cenário com pior desempenho que você pode criar.

Primeiro, a delegação de eventos nem sempre torna seu código mais rápido. Em alguns casos, é vantajoso e, em alguns casos, não. Você deve usar a delegação de eventos quando realmente precisar da delegação de eventos e quando se beneficiar dela. Caso contrário, você deve vincular manipuladores de eventos diretamente aos objetos em que o evento acontece, pois isso geralmente será mais eficiente.

Embora seja verdade que o desempenho possa ser um pouco melhor se você apenas se registrar e realizar um evento para um único elemento, acredito que ele não compensa os benefícios de escalabilidade que a delegação traz. Também acredito que os navegadores estão lidando com isso de maneira cada vez mais eficiente, embora não tenha provas disso. Na minha opinião, a delegação de eventos é o caminho a seguir!

Segundo, você NÃO deve vincular todos os eventos delegados no nível do documento. É exatamente por isso que .live () foi descontinuado porque isso é muito ineficiente quando você tem muitos eventos vinculados dessa maneira. Para manipulação de eventos delegados, é MUITO mais eficiente vinculá-los ao pai mais próximo que não é dinâmico.

Eu meio que concordo com isso. Se você tem 100% de certeza de que um evento ocorrerá apenas dentro de um contêiner, faz sentido vincular o evento a esse contêiner, mas eu ainda argumentaria contra a vinculação de eventos diretamente ao elemento acionador.

Terceiro, nem todos os eventos funcionam ou todos os problemas podem ser resolvidos com a delegação. Por exemplo, se você deseja interceptar eventos-chave em um controle de entrada e impedir que chaves inválidas sejam inseridas no controle de entrada, não é possível fazer isso com a manipulação de eventos delegados porque, quando o evento chegar ao manipulador delegado, ele já estará foi processado pelo controle de entrada e é tarde demais para influenciar esse comportamento.

Isto simplesmente não é verdade. Por favor, veja este códigoPen: https://codepen.io/pwkip/pen/jObGmjq

document.addEventListener('keypress', (e) => {
    e.preventDefault();
});

Ele ilustra como você pode impedir que um usuário digite digitando o evento de pressionamento de tecla no documento.

Aqui estão os momentos em que a delegação de eventos é necessária ou vantajosa:

Quando os objetos nos quais você está capturando eventos são criados / removidos dinamicamente e você ainda deseja capturar eventos neles, sem precisar religar explicitamente os manipuladores de eventos toda vez que cria um novo. Quando você tem muitos objetos que desejam exatamente o mesmo manipulador de eventos (onde lotes é pelo menos centenas). Nesse caso, pode ser mais eficiente no momento da configuração vincular um manipulador de eventos delegados em vez de centenas ou mais manipuladores de eventos diretos. Observe que o tratamento de eventos delegados é sempre menos eficiente em tempo de execução do que os manipuladores diretos de eventos.

Gostaria de responder com esta citação de https://ehsangazar.com/optimizing-javascript-event-listeners-for-performance-e28406ad406c

Event delegation promotes binding as few DOM event handlers as possible, since each event handler requires memory. For example, let’s say that we have an HTML unordered list we want to bind event handlers to. Instead of binding a click event handler for each list item (which may be hundreds for all we know), we bind one click handler to the parent unordered list itself.

Além disso, pesquisar no Google performance cost of event delegation googleretorna mais resultados em favor da delegação de eventos.

Quando você está tentando capturar (em um nível mais alto no seu documento) eventos que ocorrem em qualquer elemento do documento. Quando o seu design está usando explicitamente o evento bubbling e stopPropagation () para resolver algum problema ou recurso em sua página. Para entender um pouco mais, é preciso entender como os manipuladores de eventos delegados do jQuery funcionam. Quando você chama algo assim:

$ ("# myParent"). on ('clique', 'button.actionButton', myFn); Ele instala um manipulador de eventos jQuery genérico no objeto #myParent. Quando um evento de clique é associado a esse manipulador de eventos delegados, o jQuery precisa percorrer a lista de manipuladores de eventos delegados anexados a esse objeto e verificar se o elemento de origem do evento corresponde a algum dos seletores nos manipuladores de eventos delegados.

Como os seletores podem estar bastante envolvidos, isso significa que o jQuery precisa analisar cada seletor e compará-lo às características do destino do evento original para ver se ele corresponde a cada seletor. Esta não é uma operação barata. Não é grande coisa se houver apenas um deles, mas se você colocar todos os seus seletores no objeto de documento e houver centenas de seletores para comparar com cada evento com bolhas, isso poderá prejudicar seriamente o desempenho da manipulação de eventos.

Por esse motivo, você deseja configurar seus manipuladores de eventos delegados para que um manipulador de eventos delegados fique o mais próximo possível do objeto de destino. Isso significa que menos eventos serão exibidos em cada manipulador de eventos delegados, melhorando assim o desempenho. Colocar todos os eventos delegados no objeto de documento é o pior desempenho possível, porque todos os eventos com bolhas precisam passar por todos os manipuladores de eventos delegados e serem avaliados com relação a todos os possíveis seletores de eventos delegados. É exatamente por isso que .live () foi descontinuado, porque foi isso que .live () fez e provou ser muito ineficiente.

Onde isso está documentado? Se isso for verdade, o jQuery parece estar lidando com a delegação de uma maneira muito ineficiente, e meus contra-argumentos devem ser aplicados apenas ao JS vanilla.

Ainda assim: eu gostaria de encontrar uma fonte oficial que apoie essa reivindicação.

:: EDIT ::

Parece que o jQuery está realmente fazendo bolhas de eventos de uma maneira muito ineficiente (porque eles suportam o IE8) https://api.jquery.com/on/#event-performance

Portanto, a maioria dos meus argumentos aqui é válida apenas para JS vanilla e navegadores modernos.

Jules Colle
fonte