Développement web et mobile

jQuery en 2026 : lire, maintenir et retirer une base de code legacy

Débutant9 min de lectureMis à jour

Cette page a été traduite de l'anglais avec l'aide de l'IA, puis vérifiée par rapport à l'original. Lire la version anglaise

jQuery tourne encore sur une grande partie du Web : la plupart des développeurs le croisent donc en maintenance plutôt que sur un projet neuf. Ce guide explique le modèle derrière la fonction dollar, couvre la sélection, la manipulation, les événements, l'Ajax et les plugins avec des exemples concrets, puis fait correspondre chaque idiome à l'API de la plateforme qui l'a remplacé et propose un plan par étapes pour supprimer la dépendance sans tout réécrire.

Ce que vous allez apprendre

  • Expliquer les problèmes que jQuery résolvait et pourquoi ils ont pour l'essentiel disparu
  • Lire du code jQuery avec aisance : l'objet jQuery, le chaînage, le parcours du DOM, les événements et l'Ajax
  • Reconnaître les patterns de plugin et d'espace de noms des bases de code anciennes
  • Traduire les idiomes jQuery courants en API DOM standard équivalentes
  • Planifier un retrait incrémental qui garde l'application legacy livrable à chaque étape

Pendant une dizaine d'années, ajouter jQuery était la première ligne de presque tous les projets Web. La bibliothèque n'apportait ni nouvelle architecture ni modèle de composants ; elle apportait du soulagement. Les navigateurs de l'époque divergeaient sur à peu près tout — l'enregistrement des événements, l'opacité, XMLHttpRequest, l'existence même de getElementsByClassName — et jQuery masquait ces différences derrière une API concise qui se comportait partout de la même façon. Cela a suffi à en faire la bibliothèque JavaScript la plus déployée de l'histoire.

Aujourd'hui, la plupart des développeurs la rencontrent dans l'autre sens : en héritant d'une base de code qui repose sur elle. Ce guide est écrit pour cette situation. Il explique le modèle assez en profondeur pour que vous lisiez du jQuery en confiance, couvre les parties que vous croiserez réellement — sélection, manipulation du DOM, événements, Ajax, plugins — puis fait correspondre chaque idiome à l'API standard qui l'a supplanté, avec un plan par étapes pour sortir la dépendance sans geler le développement de fonctionnalités.

Les problèmes qu'il résolvait, et ce qu'ils sont devenus

Trois choses rendaient jQuery indispensable, et la plateforme les a toutes trois absorbées depuis.

L'uniformisation entre navigateurs. Attacher un événement imposait autrefois de choisir entre addEventListener et attachEvent, et l'objet événement lui-même n'avait pas les mêmes noms de propriétés partout. jQuery gommait ces écarts. Les navigateurs modernes, y compris les navigateurs evergreen qui ont remplacé l'ère des versions figées, implémentent les mêmes standards.

Une sélection concise. Avant querySelectorAll, trouver des éléments par sélecteur CSS voulait dire écrire son propre moteur ou en embarquer un. jQuery intégrait Sizzle, et la fonction dollar est devenue le raccourci de « trouve-moi ces éléments ». Le DOM a obtenu la même capacité en natif en 2009, et elle est aujourd'hui disponible partout.

Une couche Ajax utilisable. XMLHttpRequest brut était verbeux et truffé de callbacks. $.ajax l'enveloppait, ajoutait la gestion du JSON puis, plus tard, une interface proche des promesses. fetch avec async/await est à la fois plus court et mieux spécifié.

Ce que jQuery n'a jamais fourni, c'est ce dont la plupart des applications ont réellement besoin aujourd'hui : un moyen de garder le DOM synchronisé avec l'état de l'application. C'est ce manque qui a poussé la communauté vers les frameworks à composants plutôt que vers un meilleur jQuery.

L'objet jQuery, et pourquoi tout se chaîne

Presque tout le code jQuery repose sur une seule idée. Appeler $(selector) renvoie un objet jQuery (le wrapped set) : un objet semblable à un tableau qui contient zéro, un ou plusieurs éléments DOM, et qui porte toutes les méthodes jQuery. Ces méthodes agissent sur l'ensemble d'un coup — inutile de boucler — et la plupart renvoient à nouveau l'ensemble, ce qui permet le chaînage.

$(".row.expired")
  .addClass("dimmed")
  .attr("aria-hidden", "true")
  .find("button")
  .prop("disabled", true);

L'asymétrie essentielle : les setters renvoient l'ensemble, pas les getters. $(".row").text("hi") définit le texte de chaque ligne trouvée et renvoie l'ensemble ; $(".row").text() lit le texte du premier élément seulement et renvoie une chaîne, ce qui termine la chaîne d'appels. Lire du jQuery inconnu consiste en bonne partie à repérer l'endroit où une chaîne devient silencieusement une valeur.

Un objet jQuery sans correspondance n'est pas une erreur. $(".typo").hide() ne fait rien et ne signale rien, ce qui est pratique et explique aussi pourquoi des fautes de frappe dans les sélecteurs peuvent survivre des années. Vérifiez .length quand une correspondance est obligatoire.

La fonction dollar est surchargée de quatre façons : une chaîne de sélecteur, un élément ou un ensemble DOM à envelopper, une chaîne HTML à analyser pour créer de nouveaux nœuds et — celle qui piège les gens — une fonction, qui est alors enregistrée pour s'exécuter une fois le document prêt. $(function () { ... }) équivaut à $(document).ready(...).

Sélectionner, parcourir et filtrer

Les sélecteurs sont du CSS, auquel s'ajoutent des extensions propres à jQuery comme :visible, :contains() et :first. Ces extensions ne peuvent pas être déléguées au moteur du navigateur : elles sont nettement plus lentes et n'ont pas d'équivalent standard — à noter lorsque vous planifiez une migration.

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");

Les méthodes de parcours sont la partie de jQuery dont l'expressivité n'est toujours pas entièrement égalée par le DOM standard ; closest est natif désormais, mais il n'existe ni siblings() ni addBack() intégrés. Ce sont des helpers de trois lignes quand vous en avez besoin.

La sélection répétée est l'erreur de performance classique. $(".item") relance la requête à chaque appel : mettez le résultat en cache dans une variable. La convention quasi universelle qui consiste à préfixer ces variables d'un dollar existe précisément pour signaler « ceci est un objet jQuery, pas un élément ».

Modifier le document

$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

Deux distinctions comptent au moment de déboguer. attr lit et écrit l'attribut HTML ; prop lit et écrit la propriété vivante du DOM. Pour une case à cocher, attr("checked") reflète le balisage tel qu'il a été écrit tandis que prop("checked") reflète ce que l'utilisateur a réellement fait — les confondre donne des formulaires qui envoient des valeurs périmées. Et .data() ne se contente pas de lire l'attribut data-* : il s'initialise à partir de l'attribut une seule fois, puis conserve les valeurs dans un cache interne, si bien que les modifications ultérieures de l'attribut lui restent invisibles.

.html() analyse et insère du balisage, ce qui en fait un vecteur de cross-site scripting dès que la chaîne contient quoi que ce soit fourni par un utilisateur. Utilisez .text() sauf si vous maîtrisez chaque octet.

Événements et délégation

$("#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

Dans un handler, this est l'élément brut et non un objet jQuery : $(this) est donc la première ligne habituelle. Cela signifie aussi que les fonctions fléchées cassent l'idiome — un vrai piège lors de la modernisation d'un fichier, puisque convertir les handlers en fonctions fléchées fait silencieusement pointer this vers la portée englobante.

La délégation est la fonctionnalité événementielle la plus précieuse de jQuery, et elle se transpose telle quelle au code moderne : le handler vit sur un conteneur qui existe toujours, et le sélecteur est testé pendant la remontée de l'événement. Voilà pourquoi des lignes insérées dynamiquement continuent de fonctionner sans rien réattacher.

La bibliothèque uniformise aussi event.which, pageX/pageY et d'autres propriétés du même genre, et elle gère les événements personnalisés avec trigger ainsi que les handlers à espace de noms comme "click.editor", qui permettent à un plugin de retirer uniquement ses propres écouteurs avec .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

Deux comportements diffèrent de fetch et comptent quand vous portez du code. jQuery considère toute réponse hors 2xx comme un échec et appelle fail, alors que fetch résout la promesse et vous laisse vérifier response.ok. Et jQuery sérialise par défaut un objet data simple en encodage de formulaire ; envoyer du JSON impose de définir contentType et de faire le stringify vous-même. Quand vous examinez ce que renvoie réellement un endpoint legacy, le formateur JSON et l'encodeur/décodeur d'URL vous éviteront bien des minutes à plisser les yeux devant les devtools.

Les objets $.Deferred sont antérieurs aux promesses natives et ne leur sont pas entièrement conformes, même si le jQuery moderne les rend assez interopérables pour qu'on puisse les await. Dans du code plus ancien, vous croiserez aussi $.when(a, b).then(...), l'ancêtre de Promise.all.

Le pattern de plugin, et les autres idiomes de l'époque

Les plugins étendent $.fn, le prototype partagé par tous les objets jQuery. Leur forme conventionnelle ressemble à ceci :

(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);

L'expression de fonction immédiatement invoquée (IIFE) donne au plugin une portée privée et garantit que $ désigne bien jQuery, même sur une page qui appelle noConflict(). C'est return this.each(...) qui maintient la chaîne en vie. $.extend fusionne les valeurs par défaut avec les options de l'appelant — l'ancêtre du spread d'objet.

À côté des plugins, vous verrez d'autres idiomes de l'époque : un unique objet global servant d'espace de noms à toutes les fonctions, des $(document).ready imbriqués sur plusieurs niveaux, des feature flags lus dans des attributs data-* et des handlers déclarés en ligne dans le HTML. Rien de tout cela n'est faux, mais tout cela précède les modules, si bien que comprendre l'ordre de chargement est souvent le plus difficile quand on lit ce genre de code. Le guide sur les fondamentaux de JavaScript couvre les modules et la gestion de la portée qui ont remplacé ces conventions.

Idiome par idiome, en DOM standard

jQueryDOM standard
$(".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)écouteur sur le parent plus event.target.closest(".row")
$.ajax(...) / $.getJSON(url)fetch(url) puis await response.json()
$.extend({}, a, b)spread d'objet
$.each(arr, fn) / $.maparr.forEach(fn) / arr.map(fn)
$(fn)document.addEventListener("DOMContentLoaded", fn) ou un script defer
$el.hide() / $el.show()basculer une classe CSS
$el.fadeIn()une transition CSS, ou la Web Animations API

La seule ligne sans équivalent direct est la délégation, et elle est assez courte pour être écrite une fois pour toutes :

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);
  });
}

S'en débarrasser sans tout réécrire

Sur une grosse base de code, un retrait en big bang se justifie rarement. Une approche par étapes garde l'application livrable à tout moment.

  1. Commencez par l'inventaire. Cherchez $( et $. séparément avec grep, et listez les plugins. Ce sont généralement eux qui dictent le calendrier : un tooltip maison, c'est un après-midi ; un date picker ou une grille utilisés sur quarante écrans, c'est un projet à part entière.
  2. Supprimez le poids mort. Les anciens bundles embarquent souvent jQuery UI, jQuery Migrate ou plusieurs plugins pour des fonctionnalités que plus personne n'utilise. Retirer le code inutilisé est l'économie d'octets la moins chère qui soit, et cela réduit la surface à porter.
  3. Posez une frontière. Le nouveau code utilise les API standard et les modules ; les fichiers existants restent en l'état. Ajoutez une règle de lint qui interdit $ dans les nouveaux répertoires pour que la frontière ne s'estompe pas.
  4. Convertissez fichier par fichier, pas idiome par idiome. Porter tous les appels à .addClass() sur cinquante fichiers produit un diff impossible à relire. Prenez un écran, convertissez-le de bout en bout et livrez-le.
  5. Remplacez les plugins par des fonctionnalités de la plateforme. L'élément natif dialog remplace les plugins de modale, details remplace les accordéons, les transitions CSS remplacent la file d'animations, et l'Intersection Observer remplace les plugins basés sur la position de défilement.
  6. Retirez la balise script en dernier, une fois l'inventaire vide, et vérifiez-le en cherchant jQuery et $ dans le résultat du build. Mesurez avant et après : les octets économisés et la baisse du coût d'analyse se voient généralement dans les métriques présentées dans performance des applications.

Si l'application est une application AngularJS mêlée de jQuery, ordonnez le travail avec soin : la migration du framework et le retrait de la bibliothèque interagissent, et les compromis sont détaillés dans migrer d'AngularJS vers Angular.

À retenir

  • jQuery a résolu l'uniformisation entre navigateurs, la sélection concise et l'ergonomie de l'Ajax ; la plateforme fait désormais les trois, si bien qu'un nouveau projet n'y gagne rien, tandis que les projets existants en dépendent encore fortement.
  • Tout tourne autour de l'objet jQuery : les méthodes agissent sur chaque correspondance, les setters renvoient l'ensemble pour permettre le chaînage, et les getters ne lisent que le premier élément.
  • Connaissez les paires qui causent des bugs — attr contre prop, .text() contre .html(), et .data() qui lit un cache plutôt que l'attribut.
  • Les handlers délégués attachés à un ancêtre stable sont le pattern à conserver ; dans un handler, this est l'élément brut, donc les fonctions fléchées changent le sens du code.
  • Presque chaque idiome a un équivalent standard d'une ligne ; la délégation est la seule lacune, et un helper d'une dizaine de lignes la comble.
  • Retirez la dépendance fichier par fichier derrière une frontière de lint, traitez d'abord les plugins parce que ce sont eux qui dictent le calendrier, et ne supprimez la balise script qu'une fois l'inventaire vide.

Sources et lectures complémentaires

Questions fréquentes

jQuery est-il mort ?
Non, mais il n'est plus un choix par défaut. La bibliothèque est toujours maintenue et se charge encore sur un très grand nombre de sites en production : la connaître reste une compétence utile en maintenance. Ce qui a changé, c'est que ses trois arguments d'origine — l'uniformisation entre navigateurs, une sélection concise et une surcouche Ajax utilisable — sont désormais pris en charge par la plateforme. Un nouveau projet n'a donc rien à gagner à l'ajouter.
Peut-on utiliser jQuery avec un framework moderne ?
C'est possible, et les pages hybrides existent, mais considérez-le comme un état transitoire. Un framework possède le DOM qu'il rend ; si jQuery modifie ces mêmes nœuds dans son dos, vous obtenez un état que le framework ignore et des re-rendus qui effacent vos modifications sans prévenir. Si les deux doivent cohabiter, cantonnez jQuery aux zones que le framework ne touche jamais et supprimez-le zone par zone.
Pourquoi jQuery renvoie-t-il le même objet depuis presque toutes ses méthodes ?
La plupart des méthodes jQuery renvoient l'ensemble d'éléments sur lequel elles ont été appelées, et c'est ce qui rend le chaînage possible. Les setters renvoient l'ensemble pour que l'appel suivant porte sur les mêmes éléments ; les getters cassent la chaîne et renvoient une simple valeur lue sur le premier élément seulement. Cette asymétrie est de loin la première source de confusion à la lecture d'un code jQuery inconnu.
De combien une page s'allège-t-elle sans jQuery ?
La bibliothèque minifiée et compressée pèse environ 30 Ko, auxquels s'ajoutent le temps d'analyse et d'exécution à chaque chargement de page, plus les plugins qui s'appuient dessus — souvent plus lourds que la bibliothèque elle-même. Sur un site de contenu avec une poignée d'interactions, toute la dépendance remplace typiquement une cinquantaine de lignes de DOM standard : la retirer est donc presque toujours un gain net, tant pour le temps de chargement que pour la surface de dépendances.