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

jQueryDOM 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) / $.maparr.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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Troque plugins por recursos da plataforma. O dialog nativo substitui os plugins de modal, details substitui 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.
  6. Remova a tag script por último, quando o inventário estiver vazio, e confirme buscando jQuery e $ 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 — attr versus prop, .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.