Desenvolvimento web e mobile
jQuery em 2026: como ler, manter e aposentar um código legado
Iniciante9 min de leituraAtualizado
Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês
O jQuery ainda roda em boa parte da web, então a maioria dos desenvolvedores o encontra em manutenção, e não em projetos novos. Este guia explica o modelo por trás da função cifrão, cobre seleção, manipulação, eventos, Ajax e plugins com exemplos reais e, depois, relaciona cada construção com a API da plataforma que a substituiu, propondo um plano em etapas para remover a dependência sem reescrever tudo.
O que você vai aprender
- Explicar quais problemas o jQuery resolvia e por que eles praticamente deixaram de existir
- Ler código jQuery com fluência: o objeto jQuery, o encadeamento, a navegação pelo DOM, os eventos e o Ajax
- Reconhecer os padrões de plugin e de namespace usados em códigos mais antigos
- Traduzir as construções comuns do jQuery para as APIs padrão do DOM equivalentes
- Planejar uma remoção incremental que mantenha a aplicação legada pronta para ir a produção o tempo todo
Por cerca de uma década, adicionar o jQuery era a primeira linha de praticamente todo projeto web. A biblioteca não oferecia uma arquitetura nova nem um modelo de componentes; oferecia alívio. Os navegadores da época discordavam em quase tudo — registro de eventos, opacidade, XMLHttpRequest, se getElementsByClassName existia ou não — e o jQuery escondia essas diferenças atrás de uma única API concisa que funcionava igual em todo lugar. Isso bastou para torná-lo a biblioteca JavaScript mais implantada de todos os tempos.
Hoje, a maioria dos desenvolvedores o encontra pelo caminho inverso: herdando um código que depende dele para funcionar. Este guia foi escrito para essa situação. Ele explica o modelo o suficiente para que você leia jQuery com segurança, cobre as partes que você vai de fato encontrar — seleção, manipulação do DOM, eventos, Ajax, plugins — e depois relaciona cada construção com a API padrão que a substituiu, com um plano em etapas para tirar a dependência sem parar o desenvolvimento de funcionalidades.
Os problemas que ele resolvia, e o que aconteceu com eles
Três coisas tornavam o jQuery indispensável, e a plataforma absorveu todas elas.
Normalização entre navegadores. Antigamente, registrar um evento exigia escolher entre addEventListener e attachEvent, e o próprio objeto de evento tinha nomes de propriedades diferentes. O jQuery aplainava essas diferenças. Os navegadores modernos, incluindo os evergreen que substituíram a era das versões fixas, implementam os mesmos padrões.
Seleção concisa. Antes do querySelectorAll, encontrar elementos por seletor CSS significava escrever o próprio motor ou embutir um. O jQuery trazia o Sizzle, e a função cifrão virou atalho para "encontre estes elementos". O DOM ganhou a mesma capacidade de forma nativa em 2009, e hoje ela está disponível em todo lugar.
Uma camada de Ajax utilizável. O XMLHttpRequest puro era verboso e cheio de callbacks. O $.ajax o encapsulava, acrescentava tratamento de JSON e, mais tarde, uma interface parecida com promises. O fetch com async/await é mais curto e mais bem especificado.
O que o jQuery nunca ofereceu é justamente o que a maioria das aplicações precisa hoje: um jeito de manter o DOM sincronizado com o estado da aplicação. Essa lacuna é o motivo de a comunidade ter migrado para frameworks de componentes, e não para um jQuery melhor.
O objeto jQuery, e por que tudo se encadeia
Quase todo código jQuery se apoia em uma única ideia. Chamar $(selector) retorna um objeto jQuery (o wrapped set): um objeto parecido com um array que guarda zero ou mais elementos do DOM e traz todos os métodos do jQuery. Os métodos agem sobre o conjunto inteiro de uma vez — sem precisar de laço — e a maioria retorna o conjunto de novo, que é o que permite o encadeamento.
$(".row.expired")
.addClass("dimmed")
.attr("aria-hidden", "true")
.find("button")
.prop("disabled", true);
A assimetria fundamental: setters retornam o conjunto; getters, não. $(".row").text("hi") define o texto de todas as linhas encontradas e retorna o conjunto; $(".row").text() lê o texto apenas do primeiro elemento e retorna uma string, encerrando a cadeia. Ler um jQuery desconhecido é, em boa parte, perceber o ponto em que uma cadeia vira silenciosamente um valor.
Um objeto jQuery sem correspondências não é um erro. $(".typo").hide() não faz nada e não reclama de nada, o que é conveniente e também explica por que erros de digitação em seletores sobrevivem por anos. Verifique .length quando a correspondência for obrigatória.
A função cifrão tem quatro sobrecargas: uma string de seletor, um elemento ou conjunto do DOM a encapsular, uma string HTML que é interpretada para criar novos nós e — a que pega muita gente — uma função, que fica registrada para rodar quando o documento estiver pronto. $(function () { ... }) é o mesmo que $(document).ready(...).
Selecionar, navegar e filtrar
Os seletores são CSS, mais um conjunto de extensões exclusivas do jQuery, como :visible, :contains() e :first. Essas extensões não podem ser delegadas ao motor do navegador, por isso são bem mais lentas e não têm equivalente padrão — vale ter isso em mente ao planejar uma migração.
const $rows = $("#orders tbody tr");
$rows.first(); // first element, still wrapped
$rows.filter(".pending"); // narrow the set
$rows.not(".archived"); // exclude
$rows.eq(2); // by index
$rows.find("td.amount"); // descendants of the set
$row.closest("form"); // nearest matching ancestor
$row.siblings().addBack(); // siblings plus the original
$el.parent().children(".error");
Os métodos de navegação são a parte do jQuery cuja expressividade o DOM padrão ainda não alcança por completo; closest já é nativo, mas não existem siblings() nem addBack() embutidos. São helpers de três linhas quando você precisar.
Selecionar a mesma coisa repetidas vezes é o erro de desempenho clássico. $(".item") executa a consulta de novo a cada chamada, então guarde o resultado em uma variável — a convenção quase universal de prefixar essas variáveis com um cifrão existe justamente para sinalizar "isto é um objeto jQuery, não um elemento".
Alterando o documento
$el.addClass("open").removeClass("closed").toggleClass("pinned", isPinned);
$el.hasClass("open");
$el.text("Plain text, escaped");
$el.html("<em>parsed as markup</em>");
$el.val(); // form control value
$el.attr("data-state", "ready"); // HTML attribute
$el.prop("checked", true); // live DOM property
$el.data("orderId"); // reads data-order-id, with a cache
$list.append($row); // insert last inside
$row.before($banner); // insert as previous sibling
$row.remove(); // detach and clean up handlers/data
Duas distinções importam na hora de depurar. attr lê e escreve o atributo HTML; prop lê e escreve a propriedade viva do DOM. Em um checkbox, attr("checked") reflete o markup como foi escrito, enquanto prop("checked") reflete o que o usuário realmente fez — confundir os dois gera formulários que enviam valores desatualizados. E .data() não se limita a ler o atributo data-*: ele se inicializa a partir do atributo uma única vez e depois guarda os valores em um cache interno, de modo que alterações posteriores no atributo ficam invisíveis para ele.
.html() interpreta e insere markup, o que o torna um vetor de cross-site scripting sempre que a string contém qualquer coisa fornecida por um usuário. Use .text(), a não ser que você controle cada byte.
Eventos e delegação
$("#save").on("click", function (event) {
event.preventDefault();
console.log($(this).data("orderId")); // this is the raw DOM element
});
// Delegated: one handler on a stable ancestor, works for rows added later.
$("#orders").on("click", ".row .delete", function () {
$(this).closest(".row").remove();
});
$("#save").off("click");
$("#form").trigger("submit");
$el.one("transitionend", finish); // fires at most once
Dentro de um handler, this é o elemento cru, não um objeto jQuery, por isso $(this) é a primeira linha de praxe. Isso também significa que arrow functions quebram essa construção — um risco real ao modernizar um arquivo, já que converter handlers em arrow functions faz this passar a apontar, sem aviso, para o escopo externo.
A delegação é o recurso de eventos mais valioso do jQuery e se transfere diretamente para o código moderno: o handler fica em um contêiner que sempre existe, e o seletor é testado enquanto o evento sobe pela árvore. É por isso que linhas inseridas dinamicamente continuam funcionando sem precisar de um novo bind.
A biblioteca também normaliza event.which, pageX/pageY e propriedades parecidas, e suporta eventos personalizados com trigger e handlers com namespace como "click.editor", que permitem a um plugin remover apenas os próprios listeners com .off(".editor").
Ajax
$.ajax({
url: "/api/orders",
method: "POST",
contentType: "application/json",
data: JSON.stringify({ sku: "A-12", qty: 2 }),
dataType: "json",
})
.done((data) => renderOrder(data))
.fail((jqXHR, textStatus) => showError(jqXHR.status, textStatus))
.always(() => hideSpinner());
$.getJSON("/api/health", render); // shorthand
$("#results").load("/partials/results"); // fetch HTML into an element
Dois comportamentos diferem do fetch e importam na hora de portar código. O jQuery trata qualquer resposta fora da faixa 2xx como falha e chama fail, enquanto o fetch resolve a promise e deixa com você a verificação de response.ok. Além disso, o jQuery serializa por padrão um objeto data simples como dados de formulário; para enviar JSON, é preciso definir contentType e fazer o stringify você mesmo. Quando você estiver investigando o que um endpoint legado realmente retorna, o formatador de JSON e o codificador/decodificador de URL poupam você de apertar os olhos por um bom tempo no devtools.
Os objetos $.Deferred são anteriores às promises nativas e não seguem totalmente a especificação delas, embora o jQuery moderno os torne interoperáveis o bastante para usar await. Em códigos mais antigos você também vai encontrar $.when(a, b).then(...), o precursor do Promise.all.
O padrão de plugin e outras construções da época
Os plugins estendem $.fn, o protótipo compartilhado por todos os objetos jQuery. O formato convencional é este:
(function ($) {
"use strict";
$.fn.tooltip = function (options) {
const settings = $.extend({ delay: 200, position: "top" }, options);
return this.each(function () { // preserve chaining
const $el = $(this);
if ($el.data("tooltip-bound")) return; // idempotent
$el.data("tooltip-bound", true).on("mouseenter.tooltip", () => show($el, settings));
});
};
})(jQuery);
A expressão de função invocada imediatamente (IIFE) dá ao plugin um escopo privado e garante que $ signifique jQuery mesmo em uma página que chama noConflict(). É o return this.each(...) que mantém a cadeia viva. $.extend mescla os valores padrão com as opções de quem chama — o precursor do spread de objetos.
Ao lado dos plugins, você vai ver outras construções da época: um único objeto global servindo de namespace para todas as funções, wrappers $(document).ready aninhados em vários níveis, feature flags lidas de atributos data-* e handlers registrados inline no HTML. Nada disso está errado, mas tudo é anterior aos módulos, então entender a ordem de carregamento costuma ser a parte mais difícil de ler um código assim. O guia de fundamentos de JavaScript cobre os módulos e os recursos de escopo que substituíram essas convenções.
Construção por construção, em DOM padrão
| jQuery | DOM padrão |
|---|---|
$(".item") | document.querySelectorAll(".item") |
$("#id") | document.getElementById("id") |
$el.find(".x") | el.querySelectorAll(".x") |
$el.closest("form") | el.closest("form") |
$el.addClass("a").removeClass("b") | el.classList.add("a"); el.classList.remove("b") |
$el.attr("x", v) / $el.prop("checked", v) | el.setAttribute("x", v) / el.checked = v |
$el.text(s) / $el.html(s) | el.textContent = s / el.innerHTML = s |
$el.data("orderId") | el.dataset.orderId |
$parent.append($child) | parent.append(child) |
$el.remove() | el.remove() |
$el.on("click", fn) | el.addEventListener("click", fn) |
$parent.on("click", ".row", fn) | listener no pai mais event.target.closest(".row") |
$.ajax(...) / $.getJSON(url) | fetch(url) e depois await response.json() |
$.extend({}, a, b) | spread de objetos |
$.each(arr, fn) / $.map | arr.forEach(fn) / arr.map(fn) |
$(fn) | document.addEventListener("DOMContentLoaded", fn) ou um script com defer |
$el.hide() / $el.show() | alternar uma classe CSS |
$el.fadeIn() | uma transição CSS, ou a Web Animations API |
A única linha sem equivalente direto é a delegação, e ela é curta o bastante para ser escrita uma vez:
function delegate(root, type, selector, handler) {
root.addEventListener(type, (event) => {
const match = event.target.closest(selector);
if (match && root.contains(match)) handler.call(match, event);
});
}
Como se livrar dele sem reescrever tudo
Em um código grande, uma remoção em big bang raramente se justifica. Uma abordagem em etapas mantém a aplicação pronta para ir a produção a cada passo.
- Comece pelo inventário. Busque com grep
$(e$.separadamente e liste os plugins. Normalmente são os plugins que definem o cronograma: um tooltip feito em casa é uma tarde; um date picker ou uma grid usados em quarenta telas são um projeto à parte. - Elimine o peso morto. Bundles antigos muitas vezes incluem jQuery UI, jQuery Migrate ou vários plugins para recursos que ninguém mais usa. Remover código sem uso é a economia de bytes mais barata que existe e reduz a superfície que você precisa portar.
- Defina uma fronteira. Código novo usa APIs padrão e módulos; os arquivos existentes ficam como estão. Adicione uma regra de lint que proíba
$nos diretórios novos para que a fronteira não se dissolva. - Converta por arquivo, não por construção. Portar todas as chamadas a
.addClass()em cinquenta arquivos gera um diff impossível de revisar. Pegue uma tela, converta de ponta a ponta e entregue. - Troque plugins por recursos da plataforma. O
dialognativo substitui os plugins de modal,detailssubstitui os acordeões, as transições CSS substituem a fila de animações e o Intersection Observer substitui os plugins baseados na posição de rolagem. - Remova a tag script por último, quando o inventário estiver vazio, e confirme buscando
jQuerye$na saída do build. Meça antes e depois: a economia de bytes e o menor custo de parse costumam aparecer nas métricas discutidas em desempenho de aplicações.
Se a aplicação for AngularJS com jQuery misturado, ordene o trabalho com cuidado — a migração do framework e a remoção da biblioteca interagem entre si, e os trade-offs estão detalhados em migrar de AngularJS para Angular.
Principais conclusões
- O jQuery resolveu a normalização entre navegadores, a seleção concisa e a ergonomia do Ajax; a plataforma agora faz as três coisas, então projetos novos não ganham nada com ele, enquanto os existentes ainda dependem muito dele.
- Tudo gira em torno do objeto jQuery: os métodos agem sobre cada correspondência, os setters retornam o conjunto para permitir o encadeamento e os getters leem apenas o primeiro elemento.
- Conheça os pares que causam bugs —
attrversusprop,.text()versus.html()e.data(), que lê de um cache e não do atributo. - Handlers delegados em um ancestral estável são o padrão que vale levar adiante; dentro de um handler,
thisé o elemento cru, então arrow functions mudam o significado do código. - Quase toda construção tem um equivalente padrão de uma linha; a delegação é a única lacuna, e um helper de dez linhas resolve.
- Remova a dependência arquivo por arquivo atrás de uma fronteira de lint, cuide primeiro dos plugins porque são eles que definem o cronograma e só tire a tag script quando o inventário estiver vazio.
Fontes e leituras complementares
Perguntas frequentes
- O jQuery morreu?
- Não morreu, mas deixou de ser uma escolha padrão. A biblioteca continua sendo mantida e ainda é carregada em um número enorme de sites em produção, então conhecê-la é uma habilidade prática de manutenção. O que mudou é que seus três grandes atrativos originais — a normalização entre navegadores, a seleção concisa e um wrapper de Ajax utilizável — agora são resolvidos pela própria plataforma, e um projeto novo não ganha nada ao adicioná-la.
- Posso usar jQuery junto com um framework moderno?
- Pode, e páginas híbridas existem, mas trate isso como um estado temporário. Os frameworks são donos do DOM que renderizam, e o jQuery alterando esses mesmos nós pelas costas deles gera um estado que o framework desconhece e re-renderizações que descartam suas mudanças em silêncio. Se os dois precisarem conviver, restrinja o jQuery a regiões que o framework nunca toca e remova-o região por região.
- Por que o jQuery retorna o mesmo objeto em quase todos os métodos?
- A maioria dos métodos do jQuery retorna o conjunto de elementos em que foi chamada, e é isso que torna o encadeamento possível. Os setters retornam o conjunto para que a próxima chamada continue sobre os mesmos elementos; os getters quebram a cadeia e retornam um valor simples lido apenas do primeiro elemento. Essa assimetria é, de longe, a fonte mais comum de confusão ao ler um código jQuery desconhecido.
- Quanto uma página fica menor sem jQuery?
- A biblioteca minificada e comprimida tem cerca de 30 KB, somados ao tempo de parse e execução em cada carregamento de página e aos plugins que vêm por cima — muitas vezes maiores que a própria biblioteca. Em um site de conteúdo com um punhado de interações, toda a dependência costuma substituir umas cinquenta linhas de DOM padrão, então removê-la quase sempre é um ganho claro, tanto em tempo de carregamento quanto em superfície de dependências.