Tableless

Busca Menu

Desempenho e eventos jQuery: event delegation

Seja o primeiro a comentar por

Todo mundo que leva a experiência de uso da sua página um pouco a sério já se pegou pensando “uso um plugin pronto ou faço eu mesmo?” e caso cogite usar algo já pronto, acaba em dúvida sobre quais das várias opções usar.

Esses dias eu resolvi estudar o código de algumas opções disponíveis. Fiquei com medo.

Existem algumas falhas que nós desenvolvedores cometemos no desenvolvimento usando jQuery que são críticas e comuns. Hoje eu quero falar sobre uma delas em especial:

Excesso de event listeners pela página

Acho que todo mundo já escreveu um código similar a este:

$( '.foo' ).click( callback );

Você sabe o que esse código faz? Ele coloca em todos os elementos com a classe foo da sua página um event listener que dispara um callback handler sempre que o usuário clicar nele. No caso esse listener dispara a função callback. Legal e útil, né?

Agora imagine que você tenha 100 elementos com a classe foo. Serão 100 event listeners para o navegador tomar conta. Imagine que você coloque outros listeners para outros eventos e seletores. Dá pra perceber que essa conta não escala muito bem, né?

A melhor maneira de resolver isso é com event delegation.

Event Delegation

Vamos supor que nossa class foo seja aplicada à <tr> de um tipo de tabela específica. Algo assim:

<table class="tar">
    <tr class="foo">...</tr>
    <tr class="foo">...</tr>
    ...
</table>

Se a gente colocar um listener diretamente na class da tabela (.tar) mandando ele ouvir os eventos internos que ocorrerem nos elementos (.foo), vamos reduzir o número de listeners espalhados pela página.

Como se faz isso, Léo?

Assim:

$( '.tar' ).on( 'click', '.foo', callback );

Essa linha de código basicamente diz: sempre que houver um evento de clique nos elementos com classe tar selecionados, verifique se esse evento foi disparado por um elemento interno com a classe foo. Se sim, execute a função callback.

Isso pode ser feito pois os eventos do DOM normalmente são transmitidos (ou propagam) (ou propagam) à todos os seus elementos pais na árvore DOM. Esse é o caso do evento de clique.

Logo, o exemplo acima teria quase o mesmo efeito caso fosse escrito desse jeito:

$( document ).on( 'click', '.foo', callback );

Você pode adicionar um event listener para responder a cliques no documento inteiro e só executar o callback caso o clique tenha ocorrido em elementos especificados, nesse caso ‘.foo’.

Lembra que a gente está falando de melhora de desempenho? Se você forçar o navegador a ouvir todos os cliques na sua página e só executar em casos específicos, você estará disperdiçando recursos. Não faça isso, a não ser que seja extremamente necessário. Busque sempre  fixar seus eventos em elementos wrappers, como aquele <table> do exemplo.

Elementos adicionados dinâmicamente

Usar event delegation ainda garante um bônus: ter event handlers disparados por elementos que foram adicionados dinamicamente à página.

Digamos que o listener seja criado durante o document ready, como tradicionalmente é feito:

$( document ).ready(function () {
    $( '.foo' ).on( 'click', callback );
});

O handler será anexado somente aos elementos com a classe foo existentes no momento em que o documento for carregado.

Se posteriormente você criar novos elementos com a classe foo (como respostas à ações do usuário, AJAX, etc.), eles não vão ter o event listener e não ocorrerá o efeito desejado quando o usuário clica-los.

Se nós delegarmos isso para um elemento pai das <tr class=”foo”>, como no caso o <table class=”tar”>, não teremos esse problema.

As DevTools são suas amigas

É possível inspecionar o documento através do DevTools do seu navegador e identificar quais event listeners estão anexados em cada elemento. Essa pode ser uma boa estratégia inicial para auditar a sua página e verificar quais eventos podem ser anexados em elementos mais específicos, além de eliminar possíveis excessos no document.

Screenshot de uma janela do Google Chrome mostrando a aba de eventos do inspector

Aba de eventos do Chrome DevTools

Screenshot de uma janela do Mozilla Firefoz Developer Edition mostrando os de eventos no inspector

Conclusão

Cada event listener que criamos é incluído na memória utilizada pelo navegador, o excesso deles pode causar um uso excessivo de memória e deixar a sua página bem pesada. Assim como não é recomendável observar os eventos em muitos elementos, não é para fixar tudo em um único elemento pai, como o próprio document, pois você corre o risco de ter muitos listeners sendo disparados ao mesmo tempo atoa, o que pode deixar as interações de sua página bem lentas.

Nem muito específico, nem muito genérico. O importante é observar que a utilização de event delegation com o jQuery pode ser otimizada e que se deve tomar cuidado para não trocar uma má prática por outra.

No próximo artigo vou falar sobre como evitar colisão e duplicação de eventos.

Publicado no dia