Otimizar códigos jQuery em um site significa aumentar sua performance. E, como qualquer desenvolvedor web deveria saber, os componentes e recursos do front-end são responsáveis por, pelo menos, 80% da performance de páginas web!
O jQuery, em relação a JavaScript “puro”, já é bem simples de se aprender e possui uma gama de recursos incrível! Entretanto, principalmente quem está começando e/ou ainda não entendeu bem a dinâmica da biblioteca, desconhece alguma dicas simples que, se aplicadas, garantem que o desempenho seja aumentado escrevendo menos código! É isso que vamos ver neste artigo e aprender como melhorar códigos jQuery para conseguir um desempenho melhor.
Otimização de seletores
Para começar, vejamos como é possível otimizar os seletores no jQuery, otimizando as consultas realizadas e aumentando a performance do código.
Velocidade de seletores
Muito do poder do jQuery vem de sua capacidade de selecionar elementos DOM e agir/interagir com eles através de seus selectors e, através deles, o jQuery provê uma tonelada de maneiras de escolher qual(is) elemento(s) em uma página se quer trabalhar. No entanto, um número surpreendente de desenvolvedores web não sabe que os seletores não são todos iguais e que é incrível a diferença de desempenho entre 2 seletores que, à primeira vista, parecem quase idênticos.
Como exemplo, vejamos 2 maneiras de selecionar todas as tags de parágrafo dentro de uma div com id específico:
1 |
$('#id p'); |
1 |
$('#id').find('p'); |
É surpreendente o fato de que a segunda maneira pode ser 2 vezes mais rápida!
Há muitas maneiras diferentes de selecionar elementos usando jQuery; estas podem ser (virtualmente) divididas em 5 diferentes métodos. Em ordem, do mais rápido para o mais lento, são:
$('#id')
. Sem dúvidas, este é o selector mais rápido, já que mapeia diretamente odocument.getElementbyId()
nativo do JavaScript. Sempre que possível, os seletores descendentes devem ser feitos com um seletor de ID conjuntamente ao método .find(), que limita o escopo do que está sendo pesquisado (tal como no exemplo$('#id').find('p')
anterior).$('p'), $('input'), $('form'), [...]
. Selecionar elementos pelo nome da tag também é rápido, já que mapeia diretamente para o método nativodocument.getElementsByTagname()
.$(".class")
. Selecionar por nome de classes é um pouco mais custoso. Enquanto isso é executado muito bem por navegadores modernos, pode ser que uma lentidão seja causada em versões do IE baixo da 9. Por quê? Porque o IE9 foi a primeira versão do Internet Explorer a dar suporte para a funçãodocument.getElementsByClassName()
, nativa do JavaScript. Para interpretar isso, browsers mais antigos têm de recorrer ao usos de artifícios bem mais custosos, que realmente afetam o desempenho do seletor.$('[attribute="value"]')
. Não há nenhum método JavaScript nativo para este tipo seletor, então, a única maneira que pode realizar essa busca, é procurar por todo o DOM em busca de combinações que satisfaçam à condição do seletor. Navegadores modernos que suportam o métodoquerySelectorAll()
terão um desempenho melhor em certos casos, mas, em geral, este tipo de seletor é bastante lento.$(':hidden')
. Assim como o caso anterior, não existe um método JavaScript nativo para usar este tipo de seletor. Pseudo-seletores podem ser extremamente lentos, já que a consulta tem que ser executada em cada elemento da página. Novamente, navegadores modernos que suportam o métodoquerySelectorAll()
se saem ligeiramente melhores, mas tente evitar este tipo de seleção. Se realmente for necessário, é possível amenizar um pouco usando o.find()
, como visto anteriormente. Por exemplo:$('#list').find(':hidden')
.
Encadeamento
Quase todos os métodos jQuery retornam um objeto jQuery. Isto significa que, quando um método é executado, seus resultados são retornados e é possível continuar a implementação de mais métodos em sequência, o que é conhecido como encadeamento ou chaining. Ao invés de escrever o mesmo seletor várias vezes, esta característica da biblioteca permite que várias ações possam ser executadas de uma vez.
Sem encadeamento:
1 2 3 |
$('#object').addClass('active'); $('#object').css('color','#F0F'); $('#object').height(300); |
Com encadeamento:
1 |
$('#object').addClass('active').css('color','#F0F').height(300); |
Isto tem um duplo efeito ao tornar seu código mais curto e mais rápido. Métodos encadeados são executados mais rapidamente do que vários métodos de um seletor em cache e, de ambos os jeitos, são muito mais rápidos que vários métodos através de seletores sem cache. Mas, esperem… “Seletor em cache”? O que é isso?
Seletores em cache
Outra maneira fácil de melhorar a performance do código jQuery – e que parece ser um mistério para a maioria dos desenvolvedores – é a ideia de fazer cache de seletores. Pense em quantas vezes você acaba escrevendo o mesmo seletor em seus códigos. Cada $('.element')
tem que varrer todo o DOM procurando por combinações o tempo todo, independentemente se este seletor tenha sido executado antes. Executando a seleção uma vez e depois guardando o(s) resultado(s) em uma variável, significa que a busca no DOM tem que ser feita apenas uma única vez. Uma vez que os resultados de um seletor tem sido armazenada em cache, é possível fazer qualquer coisa com eles.
Primeiro, executa-se o seletor desejado (como exemplo, pegar todos os li
dentro de uma ul
de id “blocks”):
1 |
var blocks = $('#blocks').find('li'); |
Agora, é possível usar a variável blocks
em qualquer lugar que se queira, sem ter que percorrer todo o DOM novamente em cada vez:
1 2 3 4 5 6 7 |
$('#hideBlocks').click(function(){ blocks.fadeOut(); }); $('#showBlocks').click(function(){ blocks.fadeIn(); }); |
Portanto, fica essa preciosa dica: qualquer seletor que é executado mais de uma vez deve ser armazenado em cache! Veja este teste no jsperf que mostra o quão mais rápido e eficiente é um seletor cacheado em detrimento a um não cacheado.
Delegação de Evento ou Event Delegation
Event listeners consomem memória. Em sites e aplicações complexos não é incomum ter um monte de event listeners implementados e, felizmente, jQuery fornece métodos realmente fáceis para umaa gestão eficiente event listeners através da delegação.
Num exemplo extremo, imagine uma situação em que uma tabela 10×10 precisa ter um event listener em cada uma de suas células para que, quando ocorrer um clique em alguma delas, seja adiciona ou removida uma classe que define a cor de fundo da respectiva célula. Uma maneira comum de implementar isso (e algo bastante comum de ser visto em códigos jQuery) é:
1 2 3 |
$('table').find('td').click(function() { $(this).toggleClass('active'); }); |
O jQuery, a partir de sua versão 1.7, passou a oferecer um novo método de escuta de eventos: .on(). Ele age como um utilitário que envolve todos os event listeners anteriores do jQuery em um único e conveniente método e a forma que ele é implementado determina como ele se comporta. Ao reescrever o exemplo acima usando .on(), tem-se o seguinte:
1 2 3 |
$('table').find('td').on('click',function() { $(this).toggleClass('active'); }); |
Bastante simples, certo? Mas o problema aqui é que, mesmo com a mudança, o código ainda conta com um event listener para cada célula da tabela… Uma maneira muito melhor de fazer isso funcionar é criar um único event listener para monitorar os eventos na própria tabela! Uma vez que a maioria dos eventos faz bubbling (vai percorrendo seus ancestrais em sequência) na árvore do DOM, é possível vincular um único listener para um elemento (neste caso, table
) e esperar eventos serem disparados a partir de seus descendentes. A melhor maneira de fazer isso usando o .on()
é:
1 2 3 |
$('table').on('click', 'td', function() { $(this).toggleClass('active'); }); |
Com essa mudança simples, o número de event listeners passou de 100 (10×10 células da tabela) para 1! Acredite: a diferença de performance entre os 2 exemplos acima é impressionante!
Se você está usando alguma versão do jQuery abaixo da 1.7, é possível fazer a mesma coisa usando .delegate(). A sintaxe é diferente, mas uma consulta à documentação vai esclarecer as coisas.
Manipulação de DOM
Com jQuery é fácil de manipular o DOM. É trivial criar novos nós, inserir, retirar outros, mudar as coisas ao redor e assim por diante. o código para fazer isso é simples de escrever, mas, toda vez que o DOM é manipulado, o navegador tem que revisar o DOM, o que pode ser muito custoso e impactar a performance do front-end. Um exemplo evidente em que isso acontece é em um loop longo, seja através de um loop for()
, while()
ou $.each()
.
Como exemplo, vamos supor que existe um array cheio de URLs de imagens de um banco de dados ou alguma chamada em AJAX e, o que se pretende, é colocar todas essas imagens numa lista não ordenada. Comumente, o código que se encontra para isso é:
1 2 3 4 5 6 |
var arr = [reallyLongArrayOfImageURLs]; $.each(arr, function(count, item) { var newImg = '<li><img src="' + item + '"></li>'; $('#imgList').append(newImg); }); |
Existem alguns problemas com isso. Para começar, há a seleção de $('#ImgList')
em cada iteração do loop – e, como vimos anteriormente, isso não é nada bom para a performance. O outro problema é que, a cada vez que o loop repete, é adicionado um novo li
ao DOM. Cada uma dessas inserções consome recursos e, se o array é muito grande, isso poderia levar a uma grande perda de performance ou, até mesmo, ao temido alerta “Um script desta página está tornando a página lenta”…
Uma outra maneira de implementar a solução é:
1 2 3 4 5 6 7 8 |
var arr = [reallyLongArrayOfImageURLs], tmp = ''; $.each(arr, function(count, item) { tmp += '<li><img src="' + item + '"></li>'; }); $('#imgList').append(tmp); |
O que foi feito, aqui, foi a criação de uma variável tmp
para receber cada li
criado. Quando o loop termina, a variável contém todos os itens de lista na memória e estes podem ser anexados ao ul
todos de uma vez! Como navegadores trabalham melhor com operações de objetos na memória ao invés de diretamente atualizando o DOM a cada vez, este código é bem mais rápido e eficiente!
Conclusão
Logicamente, estas dicas de como otimizar códigos jQuery para aumentar a performance do front-end não são as únicas existentes, mas, certamente, estão dentre as mais simples de implementar. Embora algumas das mudanças, individualmente, façam apenas alguns milésimos de segundo de diferença, ao somar isso, dependendo de sua aplicação, é possível ter um aumento de performance de quase 70%!
Estudos mostram que o olho humano é capaz de discernir delays de 100ms (!), portanto, fazer algumas mudanças no código pode, facilmente, ter um efeito notável sobre o modo (e a percepção) de quão bem um seu site ou aplicativo é executado.
E você, tem alguma outra dica de otimização de código jQuery para compartilhar? Comente!