Préparation d'entretien 100% Gratuit angularforall.com

jQuery Senior — Architecture patterns

Jquery Senior Quiz-Jquery-Senior Architecture-Patterns Debounce Throttle Cache-Selecteurs Module-Pattern Mvc-Frontend Questions-Entretien-Jquery-Senior Qcm-Jquery-Avance Accessibilite Cross-Browser Front-End
Sénior 🔀 Mixte 20 questions ⏱ 15 min
📝

jQuery Senior — Architecture patterns

20 questions avancées jQuery : patterns architecturaux (MVC, Module Pattern), optimisation des performances (debounce, throttle, cache sélecteurs), intégration avec des APIs modernes, accessibilité et compatibilité cross-browser. Idéal pour réussir un entretien jQuery Senior.

20 questions ⏱ ~15 min Niveau Sénior

Partager

Banque de révision : les 60 questions corrigées

Voici l'intégralité des 60 questions de « jQuery Senior — Architecture patterns », chacune avec sa bonne réponse et son explication. Le quiz interactif ci-dessus en tire une sélection aléatoire à chaque tentative — utilisez cette liste complète pour réviser.

  • Il améliore les performances de sélection DOM
  • Il encapsule l'état privé et expose une API publique contrôlée (bonne réponse)
  • Il permet d'utiliser $.ajax sans configuration
  • Il désactive automatiquement les conflits de noms avec d'autres bibliothèques
Explication : Le Module Pattern avec IIFE ((function($){ ... })(jQuery);) crée une portée lexicale fermée : les variables internes sont inaccessibles depuis l'extérieur. Seul ce que le module retourne explicitement devient public. En production, cela évite la pollution de l'espace global et les collisions de variables entre scripts de tiers. Par exemple, une variable var cache = {} définie dans l'IIFE est invisible depuis la console du navigateur. C'est la base de tout plugin jQuery robuste. Sans ce pattern, une variable mal nommée peut écraser une variable d'un autre script chargé sur la même page.
En entretien senior, montrez que vous connaissez la différence entre portée de module et portée globale. Citez un cas réel : deux scripts utilisant tous les deux une variable utils globale qui entrent en conflit.

  • Le Revealing Module retourne directement les références aux fonctions privées (bonne réponse)
  • Le Revealing Module utilise $.extend() pour exposer l'API
  • Le Revealing Module n'utilise pas d'IIFE
  • Le Revealing Module est une fonctionnalité native de jQuery 3.x
Explication : Dans le Revealing Module Pattern, toutes les fonctions sont définies en privé dans la portée de l'IIFE, puis un objet littéral expose les références vers ces fonctions privées. Exemple : return { init: init, destroy: destroy };. Le pattern classique définit des fonctions directement dans l'objet retourné. L'avantage du Revealing Pattern : la cohérence — les fonctions publiques et privées sont définies de la même façon et peuvent s'appeler mutuellement. Le piège en production : si vous exposez une référence et que la fonction est redéfinie de l'extérieur, l'objet retourné ne pointe plus vers la bonne implémentation.
Citez le cas concret : en debugging, il est plus facile d'inspecter toutes les fonctions (publiques et privées) au même niveau de portée dans le Revealing Pattern.

  • _setup
  • _create (bonne réponse)
  • _build
  • _ready
Explication : Dans le Widget Factory jQuery UI, _create est appelée une seule fois lors de la création initiale du widget. Elle sert à construire la structure DOM, attacher les événements et initialiser l'état interne. _init, en revanche, est appelée à chaque fois que le widget est invoqué sans argument après sa création (permettant de le réinitialiser). En production, une erreur fréquente est de mettre du code d'événements dans _init, ce qui cause des handlers dupliqués si le widget est réinitialisé. La règle : DOM et events dans _create, reset d'état optionnel dans _init.
Piège classique en entretien : confondre _create et _init. Expliquez que _create = constructeur, _init = reset. Citez le bug des listeners dupliqués comme preuve de maîtrise.

  • En utilisant $.broadcast() sur chaque composant
  • En déclenchant et écoutant des événements personnalisés sur un objet jQuery partagé (ex: $(document)) (bonne réponse)
  • En utilisant $.message() avec un canal nommé
  • En utilisant $.fn.observe() disponible depuis jQuery 2.x
Explication : Le pattern consiste à utiliser un objet jQuery central (souvent $(document) ou un objet dédié var bus = $({});) comme event bus. Les producteurs font bus.trigger('user:login', data) et les consommateurs font bus.on('user:login', handler). Le découplage est total : les composants ne se connaissent pas. En production, utiliser $(document) fonctionne mais pollue l'espace global des événements. Préférez var EventBus = $({}); comme module partagé. Ce pattern précède les systèmes modernes de state management comme Redux et reste pertinent pour des apps jQuery legacy de grande taille.
En entretien, montrez que vous comprenez les compromis : l'event bus jQuery est simple mais difficile à déboguer (pas de trace centralisée). Proposez d'encapsuler le bus dans un module avec des méthodes nommées publish / subscribe.

  • $.cache[element.id]
  • $(element).data('pluginName', stateObject) (bonne réponse)
  • $(element).attr('data-state', JSON.stringify(state))
  • element._pluginState = stateObject
Explication : $(element).data('pluginName', stateObject) stocke les données dans le cache interne de jQuery (pas dans le DOM), ce qui évite la sérialisation/désérialisation JSON et les limitations de data-* HTML. Chaque instance du plugin peut avoir son propre état sans collision. Le pattern standard : dans _create du plugin, $(this).data('monPlugin', { options: opts, state: {} });. En production, ne pas utiliser element._pluginState (propriété JS directe) car cela crée des fuites mémoire avec les circular references, surtout dans les anciens IE. L'accès depuis une méthode publique : var data = $(element).data('monPlugin');.
Mentionnez que .data() jQuery ne synchronise PAS avec les attributs data-* HTML après l'initialisation. Si vous modifiez $(el).data('key', val), el.dataset.key restera inchangé.

  • Un plugin chainable utilise $.fn.extend(), l'autre utilise $.extend()
  • Un plugin chainable retourne this (l'objet jQuery), l'autre retourne une valeur scalaire ou objet — interrompant la chaîne (bonne réponse)
  • Un plugin chainable ne peut pas recevoir d'arguments
  • Il n'y a pas de différence fonctionnelle, c'est une convention de nommage
Explication : Le chaînage jQuery repose sur le retour de this (l'objet jQuery courant) depuis la méthode de plugin. Exemple chainable : $.fn.highlight = function() { return this.each(function(){ $(this).css('color','red'); }); }; permettant $('.el').highlight().show().addClass('done'). Un plugin qui retourne une valeur (ex: $.fn.getCount = function() { return this.length; }) interrompt la chaîne. En production, documentez clairement lequel retourne quoi : un développeur qui s'attend à du chaînage et obtient undefined perd du temps à déboguer. La convention : les plugins d'action sont chainables, les plugins de getter retournent une valeur.
Pattern de compromis : gérer les deux cas avec if (typeof option === 'string') return getter() (comme le fait jQuery UI). Exemple : $('#modal').dialog('option', 'width') retourne la valeur, $('#modal').dialog('open') est chainable.

  • Appeler $(element).remove() uniquement
  • Retirer les event listeners avec .off(), supprimer les données avec .removeData(), et nettoyer les éléments DOM créés (bonne réponse)
  • Affecter null à la variable du plugin
  • Appeler $.cleanData() sur le document
Explication : Un plugin bien conçu doit implémenter une méthode destroy qui : (1) appelle $(this).off('.monPlugin') pour retirer tous les handlers namespaced, (2) appelle $(this).removeData('monPlugin') pour nettoyer le cache jQuery, (3) supprime les éléments DOM ajoutés (overlays, wrappers), (4) restore les attributs et classes originaux. En production, omettre .off() cause des handlers fantômes qui continuent de s'exécuter même après la 'destruction' du widget. Utiliser des namespaces d'événements (.on('click.monPlugin', ...)) simplifie énormément la destruction car un seul .off('.monPlugin') retire tout.
Lors d'un entretien, citez le cas d'une Single Page Application : sans destruction propre, chaque navigation réinstancie le plugin sans détruire l'ancien, multipliant les handlers et causant des comportements aléatoires.

  • this.config.optionName
  • this.options.optionName (bonne réponse)
  • this.settings.optionName
  • this.params.optionName
Explication : Dans le Widget Factory ($.widget()), les options sont fusionnées automatiquement entre les options par défaut définies dans le prototype et celles passées à l'instanciation. Elles sont toujours accessibles via this.options.optionName. Le Widget Factory merge avec $.extend() en profondeur les options par défaut avec celles de l'utilisateur. Pour modifier une option après création : $('#el').monWidget('option', 'vitesse', 500). Utiliser this._setOption(key, value) dans le widget permet de réagir aux changements d'options en temps réel. C'est plus robuste que de passer un objet de config manuellement.
Mentionnez this._super() pour appeler la méthode parente dans une sous-classe de widget — une fonctionnalité avancée rarement connue des candidats junior.

  • $.fn.extend() fusionne en profondeur, $.extend() en surface
  • $.fn.extend() ajoute des méthodes au prototype jQuery (plugins), $.extend() étend l'objet jQuery lui-même ou fusionne des objets (bonne réponse)
  • $.fn.extend() est déprecié depuis jQuery 2.0
  • $.extend() ne peut fusionner que deux objets à la fois
Explication : $.fn.extend({ maMethode: function(){} }) ajoute maMethode au prototype de jQuery (jQuery.prototype), ce qui la rend disponible sur tous les objets jQuery : $('.el').maMethode(). C'est le mécanisme de création de plugins jQuery. $.extend(obj1, obj2) fusionne obj2 dans obj1 et le retourne. Avec $.extend(true, target, source), la fusion est récursive (deep). $.extend({}, defaults, userOptions) est le pattern standard pour fusionner des options de plugin sans muter l'objet par défaut. Une confusion entre les deux en entretien est rédhibitoire pour un poste senior.
Piège subtil : $.fn.extend(obj) est équivalent à jQuery.prototype = $.extend(jQuery.prototype, obj). Si vous ajoutez une méthode via les deux mécanismes, la dernière assignation gagne.

  • Utiliser $.Callbacks() avec flag "once"
  • Chaque composant communique uniquement via des événements sur un objet médiateur partagé, sans référence à l'autre composant (bonne réponse)
  • Utiliser $.ajax() comme couche de communication asynchrone entre composants
  • Passer les références de composants dans les options de $.widget()
Explication : Le Mediator Pattern introduit un objet tiers qui connaît tous les composants mais où les composants ne se connaissent pas mutuellement. En jQuery : var Mediator = $({});. Le filtre fait Mediator.trigger('filter:changed', {value: 'actif'});. Le tableau fait Mediator.on('filter:changed', function(e, data){ /* rafraîchir */ });. En production, ce pattern est essentiel dans les applications multi-widgets : un changement dans le widget A déclenche des mises à jour dans B, C, D sans couplage fort. Le médiateur peut aussi logger tous les messages pour le débogage. C'est l'ancêtre des architectures Flux/Redux.
En entretien, distinguez Mediator (objet central actif) de simple EventBus (objet passif). Un Mediator peut contenir de la logique de coordination, pas seulement relayer des événements.

  • $.extend('myApp.utils', { ... })
  • $.myApp = $.myApp || {}; $.myApp.utils = { ... }; (bonne réponse)
  • $.namespace('myApp.utils', function(){ ... })
  • $.fn.myApp = { utils: { ... } };
Explication : Le pattern standard pour créer un namespace jQuery est : $.myApp = $.myApp || {}; $.myApp.utils = { format: function(){...}, parse: function(){...} };. L'opérateur || protège contre l'écrasement si un autre script a déjà initialisé le namespace. Ce pattern permet d'organiser le code en sous-modules sans polluer window directement. En production, combinez avec une IIFE : (function($){ $.myApp = $.myApp || {}; $.myApp.utils = {...}; })(jQuery); pour garder les implémentations internes privées. Le namespace évite aussi les conflits entre équipes dans les grandes applications multi-équipes.
Mentionnez que ce pattern est analogue aux modules ES6, mais adapté aux environnements sans bundler. Pour une app moderne, préférez des modules ES6 et importez jQuery comme dépendance.

  • Utiliser $.fn.vanilla() comme adaptateur universel
  • Encapsuler les APIs jQuery dans des fonctions Vanilla JS pures accessibles indépendamment de jQuery (bonne réponse)
  • Utiliser jQuery.noConflict() pour basculer entre les deux
  • Réécrire tout le code jQuery en une seule passe
Explication : Le Bridge Pattern en migration jQuery→Vanilla consiste à créer une couche d'abstraction : var DOMUtils = { addClass: function(el, cls){ if(window.jQuery) $(el).addClass(cls); else el.classList.add(cls); } };. Le reste du code utilise DOMUtils.addClass() sans savoir si jQuery est présent. Cette approche permet une migration progressive : on remplace les implémentations une par une dans le bridge sans toucher au code applicatif. En production, c'est la stratégie recommandée pour les grandes applications legacy jQuery qui migrent vers du Vanilla JS ou un framework moderne. Le bridge peut aussi servir d'API stable pendant une montée de version jQuery (1.x → 3.x).
Citez des outils concrets : You might not need jQuery (youmightnotneedjquery.com) liste les équivalents Vanilla pour chaque méthode jQuery — utile comme référence pendant la migration.

  • $('#search').on('input', $.debounce(300, handler))
  • Utiliser une variable timer, annuler avec clearTimeout et relancer avec setTimeout à chaque appel (bonne réponse)
  • $('#search').delay(300).on('input', handler)
  • Utiliser $.ajax({ delay: 300 }) directement
Explication : Le debounce natif : var timer; $('#search').on('input', function(){ clearTimeout(timer); timer = setTimeout(function(){ // appel AJAX }, 300); });. La variable timer est définie dans la portée fermée (closure) pour persister entre les appels. À chaque frappe, le timer précédent est annulé et un nouveau est créé. L'appel réel n'a lieu que si 300ms s'écoulent sans nouvelle frappe. En production sur un champ de recherche avec autocomplétion, sans debounce, chaque frappe déclenche une requête AJAX — soit 10-20 requêtes pour un mot de 6 lettres. Avec debounce à 300ms, seule la dernière frappe après une pause déclenche la requête.
Distinguez debounce (attend la fin de l'activité) de throttle (limite la fréquence). Pour un champ de recherche : debounce. Pour un scroll ou resize : throttle. En entretien, donnez les deux définitions sans être demandé.

  • jQuery ne supporte pas l'événement scroll sur window
  • L'événement scroll se déclenche des dizaines à centaines de fois par seconde, surchargeant le thread principal (bonne réponse)
  • Le handler n'est appelé qu'une fois par page chargée
  • $(window) et window.addEventListener sont incompatibles
Explication : L'événement scroll sur un appui roulette déclenche facilement 60 événements par seconde (à 60fps). Si chaque handler fait des manipulations DOM, des calculs de positions ou des requêtes AJAX, le thread JS est saturé et l'interface rame. Solution avec throttle : var scrollTimer; $(window).on('scroll', function(){ if(scrollTimer) return; scrollTimer = setTimeout(function(){ // traitement; scrollTimer = null; }, 16); }); — limite à ~60 exécutions/s max. En production, une barre de navigation sticky qui recalcule sa position à chaque scroll sans throttle peut causer des FPS drops visibles. L'utilisation de requestAnimationFrame comme throttle est encore plus précise.
Mentionnez les Passive Event Listeners ({ passive: true }) : indiquent au navigateur que le handler ne fera pas preventDefault(), permettant le scroll sans attendre le JS. jQuery 3.x les supporte via $.event.special.scroll = { setup: function(){ this.addEventListener('scroll', handler, { passive: true }); } }.

  • .detach() supprime le noeud définitivement, .remove() le conserve en mémoire
  • .detach() retire l'élément du DOM mais conserve ses données jQuery et ses événements pour réinsertion ultérieure, .remove() supprime tout (bonne réponse)
  • .detach() est une méthode synchrone, .remove() est asynchrone
  • .detach() ne supprime que les enfants, .remove() supprime l'élément entier
Explication : .detach() retire physiquement l'élément du DOM (stoppant les reflows) tout en préservant les données .data() et les handlers .on() attachés via jQuery. Pattern de performance : var = $('#list').detach();, faire 1000 manipulations sur hors DOM, puis $('body').append();. Le navigateur calcule un seul reflow à la réinsertion au lieu de 1000. .remove() en revanche nettoie les données et les événements jQuery avant suppression — adapté quand l'élément est vraiment supprimé. En production, cette technique est critique pour les tableaux de données dynamiques avec tri/filtrage côté client.
Combinez avec un DocumentFragment pour les insertions massives : créer les éléments dans un fragment, les insérer en une opération. Citez la règle d'or du DOM : minimiser le nombre de reflows en regroupant les écritures.

  • Utiliser $.ajax() pour externaliser les opérations DOM
  • Grouper toutes les lectures DOM ensemble, puis toutes les écritures ensemble — ne jamais alterner lecture et écriture (bonne réponse)
  • Toujours utiliser .css() au lieu de .animate() pour les changements de style
  • Utiliser $(document).ready() pour chaque opération DOM
Explication : Le navigateur force un reflow synchrone (layout thrashing) quand vous lisez une propriété géométrique (.width(), .offset(), .scrollTop()) juste après une écriture DOM. Mauvais pattern : .each(function(){ var h = $(this).height(); $(this).css('margin-top', h+'px'); }); — déclenche N reflows. Bon pattern : lire toutes les hauteurs d'abord dans un tableau, puis appliquer toutes les écritures. En production avec FastDOM ou en natif avec requestAnimationFrame, toutes les lectures sont batchées au début du frame, toutes les écritures à la fin. jQuery ne protège pas contre ce problème — c'est la responsabilité du développeur.
En entretien, citez l'outil Chrome DevTools Performance : les barres rouges 'Layout' dans le flamegraph sont la signature visuelle du layout thrashing. Un recruteur senior appréciera que vous sachiez identifier ce problème dans les profils de performance.

  • jQuery optimise les sélecteurs de classe mais pas les sélecteurs de tag
  • Les navigateurs modernes ont des index natifs sur les classes CSS, div.myClass force un filtrage supplémentaire par tag (bonne réponse)
  • $('div.myClass') utilise Sizzle, $('.myClass') utilise querySelectorAll nativement
  • Il n'y a pas de différence de performance, c'est une question de style
Explication : Les navigateurs modernes maintiennent des index internes optimisés pour getElementsByClassName. Quand jQuery traite $('.myClass'), il peut déléguer directement à document.getElementsByClassName('myClass') (très rapide). Avec $('div.myClass'), il doit filtrer par tag après, ajoutant une étape. En jQuery 3.x avec Sizzle, querySelectorAll('.myClass') est utilisé et c'est déjà optimisé par le moteur CSS du navigateur. La vraie règle de performance : toujours fournir un contexte $('.myClass', '#container') pour limiter la portée du sélecteur. Ajouter le tag ne fait gagner que si la classe est très répandue ET appliquée à de nombreux types de tags différents.
Règle pratique : commencez le sélecteur par l'élément le plus à droite selon Sizzle (moteur droite-à-gauche). $('#parent .child') est plus efficace que $('.child') sur toute la page car il limite le scope.

  • Pour les animations de texte uniquement
  • Pour les animations haute performance synchronisées avec le cycle de rendu du navigateur, notamment les transitions complexes ou les jeux (bonne réponse)
  • $.animate() est toujours préférable car jQuery gère automatiquement le frameRate
  • requestAnimationFrame ne peut animer que les propriétés CSS, pas les propriétés jQuery
Explication : $.animate() utilise setInterval (avant jQuery 1.6) ou requestAnimationFrame (depuis jQuery 1.6+ via jQuery.fx.timer). Cependant, $.animate() a un overhead : il interpole les valeurs en JS, calcule des styles intermédiaires et force des reflows à chaque frame. requestAnimationFrame natif combiné aux transitions CSS est beaucoup plus performant car les animations CSS s'exécutent sur le thread du compositeur (GPU), sans bloquer le thread JS. En production, pour une animation de type slide ou fade complexe : CSS transitions + ajout/suppression de classes jQuery. Pour des animations procédurales (canvas, positions calculées dynamiquement) : RAF natif. jQuery animate reste utile pour des propriétés non-animables en CSS pur.
Mentionnez will-change: transform en CSS : signale au navigateur de préparer une couche GPU pour l'élément. Combiné avec jQuery qui toggle une classe CSS, c'est le pattern optimal pour des animations 60fps.

  • element.dataset est 10x plus lent car il parse le DOM
  • $(element).data() accède au cache jQuery en mémoire, element.dataset lit l'attribut HTML du DOM — lecture initiale similaire, mais .data() est plus rapide pour les mises à jour fréquentes (bonne réponse)
  • Il n'y a aucune différence de performance
  • $(element).data() ne fonctionne que sur les éléments jQuery créés dynamiquement
Explication : $(element).data('key') stocke et lit depuis jQuery.cache (objet JS en mémoire) — pas d'accès DOM. element.dataset.key lit/écrit directement les attributs data-* du noeud DOM — implique une sérialisation en string. Nuance critique : lors du premier accès, jQuery lit les attributs data-* du HTML et les met en cache. Après initialisation, $(el).data('key', val) modifie uniquement le cache jQuery, pas l'attribut HTML. Donc el.dataset.key restera à l'ancienne valeur HTML. En production, cette désynchronisation cause des bugs difficiles à identifier : ne jamais mélanger les deux APIs pour le même dataset sur le même élément.
Règle d'or : si vous utilisez jQuery, utilisez exclusivement .data(). Si vous migrez vers Vanilla JS, remplacez .data() par dataset en faisant attention à synchroniser l'état.

  • Utiliser $.lazyLoad() intégré à jQuery 3.x
  • Utiliser l'API native Intersection Observer pour détecter l'entrée dans le viewport sans écouter l'événement scroll (bonne réponse)
  • Utiliser $(window).on('scroll') avec .offset().top < $(window).scrollTop()
  • Charger toutes les images en AJAX puis les injecter
Explication : La méthode jQuery classique avec $(window).on('scroll') calcule les positions à chaque scroll — coûteux. L'Intersection Observer API est déclarative et asynchrone : le navigateur notifie quand un élément entre/sort du viewport sans calcul JS continu. Pattern : var io = new IntersectionObserver(function(entries){ entries.forEach(function(e){ if(e.isIntersecting){ $(e.target).attr('src', $(e.target).data('src')); io.unobserve(e.target); } }); }); $('img[data-src]').each(function(){ io.observe(this); });. En production, Intersection Observer utilise le thread de composition du navigateur — zéro impact sur les performances scroll. Support : tous les navigateurs modernes. Polyfill disponible pour IE11.
Mentionnez l'attribut HTML natif loading='lazy' (tous navigateurs modernes 2019+) comme solution encore plus simple pour le lazy loading d'images. Pour une app jQuery legacy, combinez les deux : IO pour les navigateurs modernes, fallback scroll jQuery.

  • Appeler $(document).off() à chaque changement de vue
  • Utiliser des namespaces d'événements et appeler .off('.namespace') lors de la destruction de chaque composant (bonne réponse)
  • Utiliser $('*').off() pour retirer tous les handlers
  • jQuery gère automatiquement le cleanup des handlers orphelins
Explication : Le pattern de namespace est critique dans les SPA jQuery. Lors de l'initialisation d'un composant : $(document).on('click.monComposant', '#btn', handler);. Lors de la destruction : $(document).off('.monComposant');. Sans namespace, .off('click') retire TOUS les handlers click du document, cassant d'autres composants. Le pattern de délégation (on sur un ancêtre) aggrave le problème car les handlers restent actifs même après suppression des éléments fils. En production dans une SPA avec routeur côté client, chaque navigation doit déclencher la destruction des composants de la vue précédente, incluant .off(), .removeData(), et la suppression des timers (clearInterval).
Outil de diagnostic : jQuery._data(document, 'events') dans la console liste tous les handlers attachés au document. Si ce nombre croît à chaque navigation, vous avez une fuite. C'est une question de débogage qu'un senior doit connaître.

  • jQuery met en cache tous les sélecteurs #id automatiquement
  • Il délègue directement à document.getElementById() — O(1) garanti par tous les navigateurs
  • jQuery court-circuite Sizzle pour les sélecteurs d'id
  • Les deux dernières réponses sont correctes (bonne réponse)
Explication : jQuery détecte les sélecteurs de type #id avec une regex rapide et appelle directement document.getElementById(id) sans passer par Sizzle. getElementById est O(1) dans tous les navigateurs car le DOM maintient une table de hachage des IDs. C'est pour ça que $('#monId') est bien plus rapide que $('div#monId') (qui passe par Sizzle) ou $('[id=monId]'). En production : utilisez des IDs pour les éléments uniques manipulés fréquemment (modales, drawers, formulaires principaux). Pour les collections d'éléments, utilisez des classes. Cette optimisation est automatique dans jQuery — pas besoin d'action particulière.
Piège : un ID dupliqué dans la page (invalide HTML) retournera le premier élément trouvé via getElementById. jQuery ne vérifie pas l'unicité. Cela peut causer des bugs silencieux dans les templates générés côté serveur.

  • .text() est plus rapide car il n'analyse pas le HTML
  • .html() exécute les scripts inline et interprète le HTML — utiliser .text() pour échapper automatiquement le contenu et prévenir les XSS (bonne réponse)
  • .text() supporte les chaînes multiligne, .html() non
  • Il n'y a pas de différence de sécurité entre .text() et .html()
Explication : .html('<script>alert(1)</script>') injecte et exécute le script — faille XSS classique. .text('<script>alert(1)</script>') échappe en &lt;script&gt; — affiché comme texte brut, inoffensif. En production, toute donnée non maîtrisée (API tierce, contenu utilisateur, paramètres URL) doit transiter par .text() ou être sanitisée avant .html(). La faille XSS via jQuery est parmi les plus répandues dans les audits de sécurité. Autres méthodes à risque : .append(userInput), .prepend(), .after(), .before() — toutes interprètent le HTML si la valeur est une chaîne.
En entretien de sécurité, citez la règle OWASP : 'Never put untrusted data into HTML'. Montrez que vous connaissez aussi les attributs à risque : $(el).attr('href', userInput) peut injecter javascript: — toujours valider les URLs avec une whitelist de protocoles.

  • Passer le token dans chaque appel $.ajax({ data: { csrf: token } })
  • Utiliser $.ajaxSetup({ headers: { 'X-CSRF-Token': token } }) une fois au chargement de la page
  • Utiliser $.ajaxPrefilter() pour intercepter et modifier chaque requête avant envoi
  • Les deux dernières réponses sont des approches valides selon le contexte (bonne réponse)
Explication : $.ajaxSetup({ headers: { 'X-CSRF-Token': token } }) configure un header par défaut pour toutes les requêtes jQuery — simple et global. $.ajaxPrefilter() offre plus de contrôle : $.ajaxPrefilter(function(options, originalOptions, jqXHR){ jqXHR.setRequestHeader('X-CSRF-Token', getCsrfToken()); }); permettant une logique conditionnelle (token différent par domaine, exclusion de certaines URLs). En production, $.ajaxSetup() est déconseillé par la documentation jQuery car il affecte aussi les plugins tiers qui utilisent $.ajax(). Préférez $.ajaxPrefilter() pour la granularité. Le token doit être lu depuis un meta tag côté HTML : $('meta[name=csrf-token]').attr('content').
Mentionnez que Laravel, Django, Rails exposent tous le CSRF token dans un meta tag ou un cookie. Le pattern jQuery correspondant est universel. Pour les APIs modernes, préférez un token JWT dans le header Authorization: Bearer.

  • $.ajaxPrefilter() s'exécute après la requête, $.ajaxSetup() avant
  • $.ajaxPrefilter() permet d'inspecter/modifier les options ET l'objet jqXHR avant chaque requête, avec accès au type de données — $.ajaxSetup() définit des valeurs par défaut globales statiques (bonne réponse)
  • Elles sont fonctionnellement identiques mais $.ajaxPrefilter() est plus récente
  • $.ajaxPrefilter() ne fonctionne que pour les requêtes JSON
Explication : $.ajaxPrefilter(function(options, originalOptions, jqXHR){ ... }) reçoit trois arguments : les options fusionnées, les options originales et l'objet XHR. Vous pouvez modifier options.url, options.data, appeler jqXHR.setRequestHeader() ou même annuler la requête avec jqXHR.abort(). Le premier argument optionnel filtre par type de données : $.ajaxPrefilter('json', fn) ne s'applique qu'aux requêtes JSON. $.ajaxSetup() ne fait que définir des valeurs par défaut dans l'objet de configuration global — pas d'accès à l'objet XHR. En production, un prefilter est utilisé pour les intercepteurs d'authentification (refresh token automatique, retry sur 401).
Citez un use case réel : intercepter les réponses 401 (token expiré) avec un prefilter qui rafraîchit le token et rejoue la requête originale. C'est l'équivalent jQuery des interceptors Axios.

  • Utiliser le paramètre retry: 3 dans $.ajax()
  • Dans le callback .fail(), rappeler la même fonction avec un compteur décrémenté et un délai
  • Utiliser $.ajaxPrefilter() avec un flag retryCount
  • Les deux dernières réponses représentent des approches valides (bonne réponse)
Explication : Pattern avec compteur dans le callback fail : function fetchData(retries){ return $.ajax({url: '/api/data'}).fail(function(jqXHR){ if(retries > 0 && jqXHR.status !== 404){ setTimeout(function(){ fetchData(retries - 1); }, 1000); } }); } fetchData(3);. Pattern avec Deferred réutilisable : encapsuler dans une fonction qui retourne une nouvelle Promise à chaque retry. Avec $.ajaxPrefilter() : stocker retryCount dans les options personnalisées et l'intercepter dans le prefilter. En production, ajoutez une backoff exponentielle : 1s, 2s, 4s entre les tentatives. Ne retentez jamais sur les erreurs 4xx (erreurs client) — seulement sur 5xx et les timeouts réseau.
Pattern Exponential Backoff : delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000. La partie aléatoire (jitter) évite que tous les clients réessaient exactement au même moment après une panne serveur.

  • $.ajax().cancel()
  • Stocker la référence jqXHR retournée par $.ajax() et appeler .abort() dessus (bonne réponse)
  • Utiliser le paramètre timeout: 0 pour annuler immédiatement
  • Détruire l'élément déclencheur avec .remove()
Explication : $.ajax() retourne un objet jqXHR (enhanced Deferred). Pour annuler : var request = null; function search(q){ if(request) request.abort(); request = $.ajax({ url: '/search', data: {q:q} }).done(handler).fail(function(jqXHR){ if(jqXHR.statusText !== 'abort') showError(); }); };. L'appel .abort() déclenche le callback .fail() avec statusText = 'abort' — vérifiez cette valeur pour ne pas afficher d'erreur à l'utilisateur. En production, ce pattern est indispensable dans les autocompléments : sans annulation, les réponses peuvent arriver dans le mauvais ordre (race condition) et afficher des résultats obsolètes.
Mentionnez le problème de race condition : sans abort, si la requête A (lancée en premier) répond après la requête B (lancée ensuite), l'UI affiche les résultats de A alors que l'utilisateur attendait B. L'abort résout ce problème architecturalement.

  • $.ajax({ url: '/upload', file: inputElement.files[0] })
  • Utiliser un objet FormData, le passer en data, et désactiver processData et contentType (bonne réponse)
  • jQuery ne supporte pas l'upload de fichiers nativement
  • Utiliser $.post() avec enctype: 'multipart/form-data'
Explication : var fd = new FormData(); fd.append('file', $('input[type=file]')[0].files[0]); $.ajax({ url: '/upload', type: 'POST', data: fd, processData: false, contentType: false }).done(handler);. processData: false empêche jQuery de sérialiser le FormData en query string. contentType: false laisse le navigateur définir le bon multipart/form-data avec le boundary correct. En production, ajoutez une barre de progression via xhr: function(){ var xhr = new XMLHttpRequest(); xhr.upload.addEventListener('progress', function(e){ var pct = (e.loaded/e.total)*100; updateProgressBar(pct); }); return xhr; }.
Attention à la taille maximale côté serveur : upload_max_filesize et post_max_size en PHP, client_max_body_size en Nginx. Côté jQuery, il est bon de valider la taille avant d'envoyer avec file.size > maxSize.

  • Il active le cache navigateur pour les requêtes cross-origin
  • Il envoie les cookies et headers d'authentification dans les requêtes cross-origin (CORS avec credentials) (bonne réponse)
  • Il désactive la vérification SSL côté navigateur
  • Il active la compression gzip pour la réponse
Explication : Par défaut, les requêtes CORS (XMLHttpRequest) n'envoient pas les cookies ni les headers d'authentification. withCredentials: true active ce comportement. Condition côté serveur : le header Access-Control-Allow-Credentials: true DOIT être présent dans la réponse, ET Access-Control-Allow-Origin ne peut pas être * (doit spécifier le domaine exact). En production, une API d'authentification sur un sous-domaine différent (api.monsite.com vs app.monsite.com) nécessite ce flag pour que les sessions fonctionnent. Omettre withCredentials fait que les cookies de session ne sont jamais envoyés — l'utilisateur paraît déconnecté sur chaque requête.
Gotcha fréquent : configurer withCredentials: true côté jQuery sans configurer Access-Control-Allow-Credentials: true côté serveur. Le navigateur bloque silencieusement la réponse. Vérifiez toujours les deux côtés.

  • $.get() n'utilise jamais le cache — c'est une propriété intrinsèque
  • $.get() peut utiliser le cache navigateur pour les requêtes GET ; ajouter cache: false dans $.ajaxSetup() ou utiliser $.ajax({ cache: false }) pour forcer un timestamp unique (bonne réponse)
  • Le cache est contrôlé uniquement par les headers serveur, jQuery n'a aucun impact
  • $.get() ajoute automatiquement un timestamp pour éviter le cache
Explication : $.get() respecte le cache navigateur par défaut pour les GET. $.ajax({ cache: false }) ajoute un paramètre _=[timestamp] à l'URL pour forcer une nouvelle requête. En production, cette option est utile pour les endpoints qui retournent des données dynamiques mais qui ne peuvent pas être configurés avec des headers Cache-Control: no-cache côté serveur. Attention : $.ajax({ cache: false }) sur les requêtes POST/DELETE n'a aucun effet (ces méthodes ne sont pas mises en cache). Exemple de bug réel : un polling AJAX toutes les 30s qui retourne toujours la même donnée car IE met en cache les GET — résolu avec cache: false.
Différenciez cache navigateur (contrôlable par jQuery via _=timestamp) et cache serveur (contrôlé par Cache-Control, ETag, Last-Modified). Une réponse serveur avec Cache-Control: no-cache ignore le flag jQuery.

  • La CSP n'affecte pas jQuery car il opère côté JS, pas HTML
  • Une CSP avec 'unsafe-inline' interdit bloque les handlers inline HTML et les scripts inline — jQuery .on() n'est pas affecté mais les attributs onclick HTML le sont (bonne réponse)
  • jQuery migre automatiquement les handlers inline vers .on() quand la CSP est détectée
  • La CSP bloque uniquement les styles inline, pas les scripts
Explication : Une CSP avec script-src 'self' (sans 'unsafe-inline') bloque l'exécution des handlers HTML inline (onclick, onload, onerror, etc.) et des balises <script> sans nonce/hash. Les handlers jQuery attachés via .on() dans des fichiers JS externes sont parfaitement compatibles — jQuery lui-même n'utilise pas d'inline handlers. Le piège en production : des composants legacy ou des bibliothèques tierces qui utilisent element.setAttribute('onclick', ...) dynamiquement (équivalent d'un inline handler) sont aussi bloqués par la CSP. jQuery UI versions anciennes peuvent générer des attributs tabindex ou des handlers inline sur certains widgets.
Migration pratique : remplacez tous les onclick='fn()' HTML par $(document).on('click', '[data-action=fn]', fn). C'est compatible CSP stricte et meilleur architecturalement.

  • Il configure le timeout global pour toutes les requêtes AJAX
  • Il permet de définir un mécanisme de transport personnalisé pour les requêtes (remplaçant XHR par ex. par une WebSocket ou un canal custom) (bonne réponse)
  • Il active la compression automatique des données envoyées
  • Il gère la reconnexion automatique en cas d'erreur réseau
Explication : $.ajaxTransport(dataType, function(options, originalOptions, jqXHR){ return { send: function(headers, callback){ ... }, abort: function(){ ... } }; }) permet de remplacer le mécanisme de transport XHR par un autre. Cas d'usage réels : (1) utiliser JSONP pour un type de données custom, (2) remplacer XHR par fetch() dans un environnement sans XHR, (3) simuler des requêtes dans les tests unitaires sans appels réseau réels, (4) utiliser une WebSocket comme transport pour des appels 'pseudo-AJAX'. C'est une API de bas niveau rarement utilisée directement, mais essentielle dans les frameworks de test comme jquery-mockjax.
En entretien, citez jquery-mockjax ou jasmine-ajax : ces bibliothèques de test utilisent $.ajaxTransport() pour intercepter les requêtes et retourner des réponses mockées sans serveur réel.

  • Il migre automatiquement le code jQuery 1.x vers 3.x sans intervention humaine
  • Il restaure des APIs dépréciées supprimées dans les versions récentes ET émet des warnings console — utile en développement pour identifier le code à migrer, JAMAIS en production (bonne réponse)
  • Il améliore les performances de jQuery sur les anciens navigateurs
  • Il permet d'utiliser jQuery 3.x avec des plugins conçus pour jQuery 1.x sans aucune modification
Explication : jQuery Migrate restaure temporairement les fonctions supprimées dans jQuery 2.x/3.x (comme .live(), .die(), .size(), $.isFunction() dépréciée) et émet des avertissements dans la console pour guider la migration. Il existe deux versions : une de développement (warnings verbeux) et une de production (silencieuse). Utiliser la version de production en production durable est un anti-pattern : vous maintenez du code techniquement mort. L'objectif est de charger Migrate, corriger tous les warnings, puis le retirer. En production permanente, il alourdit inutilement le bundle. Certains plugins legacy très populaires (jQuery UI 1.11) nécessitent Migrate avec jQuery 3.x.
Ordre de chargement correct : jQuery core → jQuery Migrate → plugins → votre code. Ne jamais charger Migrate après les plugins car certains checks s'effectuent à l'exécution.

  • jQuery 3.x ne supporte plus les sélecteurs CSS2
  • $.ajax() retourne désormais une Promise/A+ native au lieu d'un Deferred jQuery — le comportement des callbacks .then() change (résolution synchrone vs asynchrone) (bonne réponse)
  • jQuery 3.x supprime le support de document.ready
  • jQuery 3.x ne supporte plus les événements délégués
Explication : Le changement le plus subtil et impactant : en jQuery 1.x/2.x, le callback .then() d'un Deferred pouvait être appelé synchronement si la valeur était déjà résolue. jQuery 3.x aligne le comportement sur Promises/A+ : les callbacks .then() sont TOUJOURS asynchrones (microtask). Du code qui supposait une résolution synchrone casse silencieusement. Autres breaking changes majeurs : $.ajax() rejette maintenant correctement les erreurs HTTP (4xx/5xx), $(document).ready(fn) ne catch plus les exceptions, l'ordre des paramètres de certains callbacks change. La migration 1.x → 3.x nécessite un audit complet du code Deferred.
Pattern de migration Deferred vers Promise native : remplacez $.Deferred() par new Promise() progressivement. Mais attention : jQuery Deferred permet la résolution depuis l'extérieur (deferred.resolve()), les Promises natives non.

  • La version slim n'inclut pas le support des animations
  • La version slim exclut les modules ajax, effects (animations), et deprecated — ~6Ko plus légère (bonne réponse)
  • La version slim est optimisée pour les mobiles uniquement
  • La version slim n'inclut pas Sizzle et utilise querySelectorAll natif
Explication : jQuery Slim exclut : le module ajax ($.ajax, $.get, $.post, $.getJSON...), le module effects ($.fn.animate, $.fn.fadeIn, $.fn.slideUp...), et les méthodes dépréciées. Elle reste environ 6Ko plus petite que la full en minifié+gzip. En production, utilisez la version slim si vous utilisez fetch() natif pour les requêtes AJAX et des transitions CSS pour les animations. Cas typique : Bootstrap 5 recommande jQuery slim car Bootstrap 5 n'utilise pas $.ajax(). Attention : beaucoup de plugins jQuery nécessitent le module effects ou ajax — vérifiez la compatibilité avant d'adopter slim.
Metrics réels : jQuery 3.7 full minifié+gzip ≈ 30Ko, slim ≈ 24Ko. Sur mobile 3G, ~2ms de différence. L'avantage n'est pas dramatique, mais pour un site haute performance visant un score Lighthouse > 95, chaque Ko compte.

  • import jQuery from 'cdn.jsdelivr.net/jquery'
  • import $ from 'jquery'; — jQuery est livré avec un export ES module depuis la version 3.x (bonne réponse)
  • require('jquery') est la seule façon d'importer jQuery dans un bundler
  • jQuery ne peut pas être utilisé avec des modules ES6
Explication : Depuis jQuery 3.x, le package npm inclut un export compatible avec les bundlers ES module. import $ from 'jquery'; fonctionne avec Webpack, Rollup, Vite, esbuild. Pour exposer $ globalement (nécessaire pour les plugins legacy) avec Webpack : plugins: [new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' })]. En production, tree-shaking partiel est possible avec les builds ES module de jQuery 3.6+. Sans tree-shaking, importer jQuery tire tout le bundle. Pour une migration progressive, vous pouvez faire coexister import $ from 'jquery' dans les nouveaux modules et window.jQuery pour les scripts legacy.
Piège Webpack : si un plugin jQuery (ex: Select2) fait require('jquery') et que vous fournissez aussi window.$, il peut y avoir deux instances de jQuery — les plugins s'attachent à l'une, votre code utilise l'autre. Utilisez ProvidePlugin ou un alias dans webpack.config pour garantir une instance unique.

  • document.on('#el', 'click', handler)
  • document.querySelector('#el').addEventListener('click', handler) (bonne réponse)
  • window.onclick = handler
  • document.getElementById('el').onclick = handler
Explication : document.querySelector('#el').addEventListener('click', handler) est l'équivalent direct. Pour la délégation d'événements ($(document).on('click', '.btn', handler)), l'équivalent natif est : document.addEventListener('click', function(e){ if(e.target.matches('.btn')){ handler.call(e.target, e); } });. L'option D (element.onclick = handler) ne permet qu'un seul handler — le second écraserait le premier. Pour les collections ($('.items').on('click', handler)) : document.querySelectorAll('.items').forEach(el => el.addEventListener('click', handler)). En 2024+, Vanilla JS couvre 95% des cas jQuery — la migration réduit le poids de ~30Ko gzip.
Mentionnez AbortController pour le cleanup des event listeners Vanilla : const ctrl = new AbortController(); el.addEventListener('click', handler, { signal: ctrl.signal }); ctrl.abort(); // retire le listener. Bien plus élégant que removeEventListener.

  • $.parseJSON() ne supportait pas les tableaux JSON
  • JSON.parse() est natif (implémenté en C++ dans le moteur JS) et donc plus rapide ; $.parseJSON() n'ajoutait qu'une validation supplémentaire qui n'est plus nécessaire avec les navigateurs modernes (bonne réponse)
  • $.parseJSON() était incompatible avec les caractères Unicode
  • $.parseJSON() a été retiré pour des raisons de sécurité (faille XSS)
Explication : $.parseJSON() était un wrapper autour de JSON.parse() qui ajoutait une validation supplémentaire pour les anciens navigateurs (IE6-8) qui avaient des implémentations JSON défectueuses. Depuis jQuery 3.0, ce wrapper est jugé inutile (IE6-8 n'est plus supporté) et déprécié. JSON.parse() natif est exécuté par le moteur V8/SpiderMonkey en C++, plusieurs fois plus rapide que tout code JS. En production, remplacez systématiquement $.parseJSON(data) par JSON.parse(data). Idem pour $.type()typeof/Array.isArray(), $.isFunction()typeof fn === 'function' (déprécié jQuery 3.3), $.trim()String.prototype.trim().
Script de migration rapide : grep -r '\$.parseJSON\|\$.trim\|\$.isFunction\|\$.type\|\$.now\|\$.isNumeric' src/ pour lister tous les appels jQuery dépréciés à remplacer en priorité.

  • Ils sont tous équivalents et interchangeables
  • $.type() utilise Object.prototype.toString pour des types précis ('array', 'null', 'regexp') ; typeof ne distingue pas null/object/array ; instanceof échoue cross-iframe (bonne réponse)
  • typeof est le seul à détecter les primitives, les autres ne fonctionnent que sur les objets
  • $.type() est déprécié et ne doit jamais être utilisé en code nouveau
Explication : typeof null === 'object' (bug historique JS) et typeof [] === 'object' — impossible de distinguer null, array, et plain object. $.type(null) === 'null', $.type([]) === 'array', $.type(/re/) === 'regexp' — précis. [] instanceof Array retourne false si l'array vient d'un autre iframe (différente référence au constructeur Array). Solution moderne : Array.isArray(val) (immune aux iframes), val === null (pour null), Object.prototype.toString.call(val) === '[object Array]' (le mécanisme interne de $.type()). En code nouveau, évitez $.type() (déprécié 3.3) et utilisez les méthodes ES6+ natives.
Le piège iframe est réel : dans les applications avec iframes (éditeurs, widgets), toujours utiliser Array.isArray() plutôt qu'instanceof Array.

  • bind(), unbind(), live(), die(), delegate(), undelegate() (bonne réponse)
  • attach(), detach(), subscribe(), unsubscribe()
  • listen(), unlisten(), watch(), unwatch()
  • connect(), disconnect(), register(), unregister()
Explication : jQuery a unifié la gestion des événements dans .on() et .off() (jQuery 1.7) remplaçant : .bind()/.unbind() (handlers directs), .live()/.die() (délégation sur document — très inefficace), .delegate()/.undelegate() (délégation sur ancêtre). Équivalences : $('#el').bind('click', fn)$('#el').on('click', fn), $(document).delegate('.btn', 'click', fn)$(document).on('click', '.btn', fn). .live() était particulièrement problématique : attachait le handler au document pour tous les éléments matchant le sélecteur, même pas encore dans le DOM — performance désastreuse sur les grandes pages.
Anecdote de production : une application utilisant 50 appels .live() avait tous les handlers attachés au document, créant une cascade d'événements bulling à chaque clic. Migration vers .on() avec délégation ciblée a réduit le temps de traitement des clics de 80%.

  • Utiliser $(modal).focus() au moment de l'ouverture uniquement
  • Intercepter Tab/Shift+Tab dans le keydown de la modale et déplacer le focus manuellement vers le premier/dernier élément focusable si on sort des bornes (bonne réponse)
  • Ajouter tabindex="-1" sur tous les éléments hors modale
  • Utiliser $.fn.focusTrap() disponible dans jQuery UI
Explication : Pattern focus trap complet : à l'ouverture de la modale, stocker l'élément focusé avant ouverture. Écouter keydown sur la modale : si key === 'Tab', calculer les éléments focusables (a, button, input, select, textarea, [tabindex]:not([tabindex='-1'])), détecter le premier/dernier, et si on appuie Tab sur le dernier → déplacer sur le premier (et inversement avec Shift+Tab). À la fermeture : $(triggerElement).focus() pour restaurer le focus. En production, omettre le focus trap rend la modale inaccessible au clavier : l'utilisateur peut naviguer Tab hors de la modale dans le contenu masqué. C'est une exigence WCAG 2.1 niveau AA.
Listez les éléments focusables avec : .find('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])').filter(':visible'). Le filtre :visible est crucial.

  • Ils sont strictement équivalents pour les champs de formulaire
  • .prop() modifie la propriété DOM JavaScript (état réel) ; .attr() modifie l'attribut HTML (valeur initiale) — pour disabled/checked/selected, toujours utiliser .prop() (bonne réponse)
  • .attr() est plus compatible navigateur pour les attributs booléens
  • .prop() ne fonctionne que sur les éléments natifs HTML5
Explication : Les attributs HTML reflètent l'état initial, les propriétés DOM l'état actuel. Pour disabled, checked, selected, readonly : utilisez toujours .prop(). $(el).attr('disabled', 'disabled') peut sembler fonctionner visuellement, mais $(el).attr('disabled') retourne la string 'disabled' même après $(el).removeAttr('disabled') dans certains cas edge. $(el).prop('disabled', false) active l'élément de manière fiable. Le piège classique : if($(el).attr('checked')) retourne toujours la valeur initiale de l'attribut HTML, pas l'état actuel de la case à cocher. $(el).prop('checked') retourne l'état réel.
Règle mnémotechnique : .attr() pour les attributs qui ne changent pas (href, src, data-*, class, id), .prop() pour les états qui changent dynamiquement (checked, disabled, selected, value).

  • $(button).data('aria-expanded', true)
  • $(button).attr('aria-expanded', isOpen.toString()) — en passant une string "true"/"false" (bonne réponse)
  • $(button).prop('ariaExpanded', isOpen)
  • $(button).addClass('aria-expanded')
Explication : Les attributs ARIA sont des attributs HTML, donc .attr() est la méthode correcte. Critique : la valeur doit être une string 'true' ou 'false', pas un booléen JS. Les lecteurs d'écran lisent la valeur de l'attribut HTML comme texte. $(button).attr('aria-expanded', true) (booléen) est converti en string par jQuery → OK en pratique. Mais $(button).prop('ariaExpanded', ...) utilise la propriété IDL ARIA (disponible en Vanilla depuis Chrome 81) — non supporté par jQuery standard. Pattern accordéon complet : .attr('aria-expanded', !isOpen); .attr('aria-hidden', isOpen); .toggleClass('is-open', !isOpen);.
Testez toujours avec un lecteur d'écran réel (NVDA + Firefox, VoiceOver + Safari). L'inspecteur d'accessibilité Chrome DevTools montre les propriétés ARIA calculées, mais le comportement annoncé peut différer selon le lecteur.

  • $('#message').alert('Message pour les AT')
  • Injecter le texte dans un élément avec aria-live="polite" déjà présent dans le DOM au chargement de la page (bonne réponse)
  • Créer dynamiquement un élément avec aria-live et l'injecter dans le DOM
  • Utiliser $('#msg').attr('role', 'alert') au moment de l'injection du texte
Explication : Les live regions ARIA (aria-live) doivent être présentes dans le DOM au chargement de la page pour que les lecteurs d'écran les 'observent'. Si vous créez dynamiquement un élément avec aria-live et l'injectez, les lecteurs d'écran ne l'annoncent pas (comportement non fiable). Pattern correct : <div id='announcer' aria-live='polite' aria-atomic='true' class='sr-only'></div> dans le HTML initial. Puis : $('#announcer').text(''); setTimeout(function(){ $('#announcer').text(message); }, 100);. Le reset+timeout force les lecteurs à détecter le changement. aria-live='assertive' pour les erreurs critiques, 'polite' pour les confirmations.
Le class sr-only (Bootstrap) cache visuellement la live region mais la garde accessible aux AT. Pattern alternatif : role='status' implique aria-live='polite', role='alert' implique aria-live='assertive'.

  • Utiliser tabindex sur chaque résultat et laisser le navigateur gérer
  • Écouter keydown sur le champ parent, intercepter ArrowUp/ArrowDown/Enter, gérer le focus manuellement avec .focus() et aria-activedescendant (bonne réponse)
  • Utiliser le plugin jQuery UI Autocomplete qui gère tout automatiquement
  • Convertir chaque résultat en bouton et utiliser la navigation Tab native
Explication : Le pattern de liste de résultats navigable au clavier : (1) le champ input a role='combobox' et aria-controls='listId'. (2) La liste a role='listbox'. (3) Chaque item a role='option'. (4) Écouter keydown sur l'input : ArrowDown/Up déplace un index de sélection, mettre à jour aria-activedescendant sur l'input avec l'ID de l'option courante, et $('#'+activeId).addClass('is-focused').siblings().removeClass('is-focused'). (5) Enter déclenche la sélection. Ne jamais déplacer le focus DOM réel dans la liste — cela ferait perdre le focus du champ. aria-activedescendant est la solution standard pour indiquer l'option 'active' sans déplacer le focus.
La différence entre focus DOM et aria-activedescendant est le piège le plus fréquent. En entretien senior accessibility, nommez les rôles ARIA (combobox, listbox, option) sans hésitation.

  • jQuery détecte automatiquement prefers-reduced-motion et désactive les animations
  • Détecter avec window.matchMedia('(prefers-reduced-motion: reduce)') et conditionner l'appel à .animate()/.fadeIn()/.slideDown() (bonne réponse)
  • Utiliser le paramètre accessibility: true dans $.fx.speeds
  • Ajouter une classe CSS no-animations sur body et utiliser !important pour outrepasser jQuery
Explication : jQuery ne gère pas prefers-reduced-motion nativement. Pattern : var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;. Puis adapter : if(reducedMotion){ $('#el').show(); } else { $('#el').fadeIn(400); }. Solution globale : if(reducedMotion) $.fx.off = true; — désactive TOUTES les animations jQuery en une ligne. Mais $.fx.off = true affecte aussi les animations de plugins — préférez les conditions locales pour un contrôle fin. En production, tester avec : macOS → Accessibilité → Réduire le mouvement, ou DevTools → Rendering → Emulate CSS media feature prefers-reduced-motion.
Mentionnez que $.fx.off = true est aussi utile en tests automatisés (Selenium/Playwright) pour accélérer les tests en désactivant les animations jQuery qui causent des timeouts.

  • :visible vérifie uniquement display et visibility, mais un élément peut être visuellement visible et masqué aux AT via aria-hidden="true" ou vice versa (bonne réponse)
  • :visible ne fonctionne pas sur les éléments avec position: fixed
  • :visible ne détecte pas les éléments masqués par z-index
  • :visible est déprécié depuis jQuery 3.x
Explication : :visible vérifie que l'élément a des dimensions (display !== 'none', visibility !== 'hidden', opacity > 0 partiel). Mais : (1) Un élément avec aria-hidden='true' est visible visuellement mais masqué aux lecteurs d'écran. (2) Un élément avec .sr-only (position absolute, clip) est invisible visuellement mais accessible aux AT. (3) Un élément dans le DOM mais hors viewport peut être lu par les AT. En production, pour savoir si un AT lit l'élément : vérifiez l'absence de aria-hidden='true' sur l'élément et ses ancêtres, et la présence de role et tabindex appropriés. Il n'existe pas de méthode jQuery simple qui replique exactement l'arbre d'accessibilité — utilisez les DevTools Accessibility.
Méthode de vérification : dans Chrome DevTools, onglet Accessibility → Accessibility Tree. Un noeud en grisé avec une icône 'oeil barré' est caché aux AT. Citez cette procédure en entretien pour montrer que vous testez réellement l'accessibilité.

  • $(window).on('keydown', function(e){ if(e.keyCode === 27) $('#modal').hide(); })
  • Attacher le handler sur document, vérifier e.key === 'Escape', fermer la modale ET restaurer le focus sur l'élément déclencheur (bonne réponse)
  • Utiliser tabindex="-1" sur la modale pour capturer les événements clavier
  • Déclencher un click sur le bouton de fermeture via $('#close-btn').trigger('click')
Explication : Le pattern complet : (1) Stocker la référence du déclencheur avant ouverture : var = $(this);. (2) Attacher le handler Escape sur document avec namespace : $(document).on('keydown.modal', function(e){ if(e.key === 'Escape') closeModal(); });. (3) La fonction closeModal : cache la modale, retire le handler ($(document).off('keydown.modal')), et restaure le focus : .focus(). Utiliser e.key === 'Escape' au lieu de e.keyCode === 27 (déprécié). En production, omettre la restauration du focus laisse l'utilisateur clavier 'perdu' dans la page — WCAG 2.1 Success Criterion 2.1.2.
L'option C est subtile mais incorrecte : tabindex='-1' permet de recevoir le focus programmatiquement mais ne route pas les keydown vers l'élément si le focus n'est pas dessus. Le handler sur document est la bonne approche car il intercepte l'événement en phase de bubbling.

  • Sizzle parse les sélecteurs de gauche à droite comme CSS natif
  • Sizzle parse les sélecteurs de droite à gauche pour minimiser le nombre de noeuds évalués, puis délègue à querySelectorAll si possible (bonne réponse)
  • Sizzle utilise uniquement querySelectorAll et n'a pas de parser propre
  • Sizzle utilise le DOM walker récursif en profondeur uniquement
Explication : Sizzle (remplacé par un moteur plus léger dans jQuery 3.x) utilisait l'évaluation droite-à-gauche : pour div.container p.active a, Sizzle trouvait d'abord tous les <a>, puis vérifiait si leur ancêtre était un p.active, puis si c'était dans un div.container. C'est généralement plus efficace car les sélecteurs de droite sont plus spécifiques. jQuery 3.x préfère d'abord déléguer à querySelectorAll (nativement optimisé par le moteur CSS du navigateur) et n'utilise Sizzle que pour les pseudo-sélecteurs non-standards (:visible, :eq(), :first) que querySelectorAll ne supporte pas.
En entretien, citez le cas pratique : $(':visible') force Sizzle sur TOUT le DOM — extrêmement coûteux. Toujours fournir un contexte : $('#container :visible') ou mieux $('#container').find(':visible').

  • Les deux retournent un tableau de noeuds DOM natifs
  • $('div') retourne un objet jQuery (collection jQuery), $('div').get() retourne un tableau JavaScript natif de noeuds DOM (bonne réponse)
  • $('div') retourne le premier élément, $('div').get() retourne tous les éléments
  • $('div').get() et $('div').toArray() ont des comportements différents
Explication : $('div') retourne un objet jQuery avec toutes les méthodes jQuery disponibles (.css(), .on(), etc.). $('div').get() retourne un tableau JS natif (Array) de noeuds DOM natifs — sans méthodes jQuery. $('div').get(0) retourne le premier noeud DOM natif (équivalent à $('div')[0]). $('div').toArray() est identique à $('div').get(). En production, utilisez .get() quand vous devez passer les éléments à une API non-jQuery qui attend des noeuds DOM natifs, ou quand vous voulez utiliser les méthodes Array natives (filter, map, reduce) non disponibles sur l'objet jQuery.
Piège : $('div')[0] retourne le noeud DOM natif du premier élément (accès par index sur l'objet jQuery qui est array-like). $('div').get(0) fait la même chose mais de manière plus explicite et lisible.

  • jQuery utilise des Proxies ES6 pour intercepter chaque appel de méthode
  • Chaque méthode jQuery retourne this (l'objet jQuery courant) permettant d'appeler la méthode suivante sur le même objet (bonne réponse)
  • jQuery copie l'objet à chaque étape pour permettre le chaînage immutable
  • Le chaînage est implémenté avec des Promises qui se résolvent séquentiellement
Explication : Le chaînage jQuery est d'une simplicité élégante : chaque méthode qui ne doit pas retourner une valeur spécifique retourne this (l'objet jQuery courant). Exemple simplifié : jQuery.fn.addClass = function(cls){ this.each(function(){ this.classList.add(cls); }); return this; };. Ainsi $('.el').addClass('active').show().css('color','red') travaille toujours sur le même objet jQuery. Exception : les méthodes 'getter' (.val(), .text(), .attr() sans setter) retournent la valeur — cassant la chaîne. .end() remonte d'un niveau dans la chaîne en retournant l'objet jQuery parent stocké dans this.prevObject.
Mentionnez .end() pour montrer votre maîtrise avancée : $('#list').find('.active').css('color','red').end().find('.inactive').css('color','gray') — travaille sur le même $('#list') pour les deux find().

  • Les deux sont identiques — 'memory' est ignoré avec 'once'
  • 'once' exécute la liste une seule fois ; 'once memory' exécute une seule fois ET rappelle immédiatement tout nouveau callback ajouté après la déclenchement avec la valeur mémorisée (bonne réponse)
  • 'memory' active la mise en cache des callbacks pour améliorer les performances
  • 'once memory' permet d'annuler le déclenchement si un callback retourne false
Explication : $.Callbacks() est la liste de callbacks de bas niveau qui sous-tend le système Deferred de jQuery. Flags disponibles : once (déclenche une seule fois), memory (mémorise les arguments du dernier déclenchement), unique (callback ajouté une seule fois), stopOnFalse (arrête si un callback retourne false). Avec 'once memory' : après le premier déclenchement, tout .add(fn) ultérieur appelle immédiatement fn avec les arguments mémorisés — c'est exactement le comportement d'une Promise résolue. C'est le mécanisme interne de jQuery Deferred : $.Deferred() utilise deux listes Callbacks('once memory') pour les resolved/rejected callbacks.
Comprendre $.Callbacks() est rarement nécessaire en pratique, mais démontre une maîtrise des internals jQuery. Mentionnez que c'est la base de $.Deferred() — un candidat senior qui connaît les internals marque des points.

  • C'est uniquement pour les plugins jQuery UI
  • Définir $.event.special['monEvent'] = { setup: fn, teardown: fn, handler: fn } pour attacher une logique native lors du premier .on() et du dernier .off() (bonne réponse)
  • Utiliser $.fn.on('monEvent', { special: true }, handler)
  • $.event.special est une API interne non documentée et ne doit jamais être utilisée
Explication : $.event.special permet de définir des événements personnalisés avec cycle de vie. Exemple : $.event.special.tripleclick = { setup: function(){ $(this).on('click.tripleclick', handler); }, teardown: function(){ $(this).off('click.tripleclick'); } };. Le setup est appelé la première fois que .on('tripleclick', ...) est utilisé sur un élément, teardown quand le dernier handler est retiré avec .off('tripleclick'). Cas d'usage réels : créer des événements composés (triple-click, long-press, swipe), normaliser des événements cross-browser avec polyfills, créer un événement de 'hashchange' polyfillé pour IE7.
jQuery utilise $.event.special en interne pour les événements focus/blur (transformés en focusin/focusout pour la délégation), et pour load/beforeunload. Connaître cette API démontre une compréhension profonde des internals.

  • Elle retourne la file d'attente AJAX en cours pour un élément
  • Elle retourne ou manipule la file d'attente d'animations/fonctions de l'élément — permet d'intercaler du code custom dans une séquence d'animations (bonne réponse)
  • Elle crée une liste d'événements à déclencher séquentiellement
  • Elle est obsolète depuis l'introduction des Promises jQuery
Explication : La queue jQuery est une file FIFO de fonctions attachées à un élément. Chaque .animate(), .fadeIn(), etc. ajoute une fonction dans la queue 'fx'. Pour insérer du code custom dans une séquence : $('#el').fadeIn(400).queue(function(next){ monCode(); next(); }).fadeOut(400);. Le paramètre next est la fonction qui déclenche le prochain item de la queue. Sans appeler next(), la queue se bloque. Vous pouvez aussi utiliser .delay(1000) (qui ajoute un timeout dans la queue), .clearQueue() (vide la queue), et .dequeue() (déclenche le prochain item manuellement).
Pattern avancé : utiliser la queue pour séquencer des opérations AJAX. Mais en 2024+, les Promises/async-await sont bien plus lisibles pour cette tâche. Mentionnez que la queue est utile dans les contextes legacy sans support Promises.

  • $(el).events()
  • jQuery._data(el, 'events') — accès à l'objet interne des événements jQuery (bonne réponse)
  • $(el).data('events') — disponible depuis jQuery 1.8
  • $.fn.getEvents(el)
Explication : Depuis jQuery 1.8, $(el).data('events') ne retourne plus les événements internes (changement de comportement). La méthode correcte pour le débogage est jQuery._data(el, 'events') (ou $._data(el, 'events')) — accès direct aux données internes non documentées. Retourne un objet avec les noms d'événements comme clés et des tableaux de handlers comme valeurs. Utile pour diagnostiquer les fuites mémoire (handlers non retirés) et les handlers fantômes. En production, ne jamais s'appuyer sur _data dans le code applicatif car c'est une API interne susceptible de changer. Utiliser uniquement en débogage ou dans des outils de test.
Workflow de débogage : console.log($._data($('#myEl')[0], 'events')) dans la console. Si vous voyez des dizaines de handlers cumulés après plusieurs navigations dans une SPA, vous avez une fuite mémoire à corriger.

  • Il met en pause toutes les requêtes AJAX en attente
  • Il retarde le déclenchement de $(document).ready() jusqu'à ce que $.holdReady(false) soit appelé — utile pour charger des dépendances dynamiquement avant ready (bonne réponse)
  • Il désactive temporairement les animations jQuery
  • Il attend que tous les scripts asynchrones soient chargés avant d'exécuter
Explication : $.holdReady(true) incrémente un compteur interne qui bloque le déclenchement du DOM ready. Chaque appel à $.holdReady(false) décrémente ce compteur — quand il atteint 0 et que le DOM est prêt, ready se déclenche. Cas d'usage : charger un plugin via $.getScript() avant d'exécuter le code ready. Pattern : $.holdReady(true); $.getScript('monPlugin.js', function(){ $.holdReady(false); });. En production moderne, ce pattern est largement remplacé par les imports dynamiques ES6 (import()) et les bundlers. $.holdReady() est déprécié dans jQuery 3.2.
Anecdote historique : $.holdReady() était la solution jQuery avant AMD/RequireJS pour garantir l'ordre de chargement des dépendances. Aujourd'hui, remplacez par import('module').then(fn) ou le script loading natif avec defer.

  • $.css.add('transform', { get: fn, set: fn })
  • $.cssHooks['transform'] = { get: function(el){ ... }, set: function(el, value){ ... } } (bonne réponse)
  • $.fn.css.extend({ transform: function(){ ... } })
  • Les cssHooks ne peuvent s'appliquer qu'aux propriétés CSS standard
Explication : $.cssHooks permet d'intercepter .css('propertyName') et .css('propertyName', value) pour une propriété spécifique. Exemple pour normaliser transform avec préfixes vendors (contexte historique pré-2015) : $.cssHooks['transform'] = { get: function(el){ return el.style.transform || el.style.webkitTransform || ''; }, set: function(el, val){ el.style.transform = val; el.style.webkitTransform = val; } };. Cas d'usage modernes : créer un hook pour une propriété CSS custom (--ma-variable), normaliser des unités (.css('rotate', 45) → convertit en degrés automatiquement), ou calculer des valeurs composites.
Pattern avancé : utiliser $.cssHooks conjointement avec $.cssNumber pour les propriétés qui ne prennent pas de 'px' ($.cssNumber['opacity'] = true). jQuery l'utilise en interne pour opacity, zIndex, fontWeight.

  • Il permet de définir des règles de validation pour les champs de formulaire
  • Il intercepte .val() get/set pour des types d'éléments spécifiques — jQuery l'utilise pour normaliser les checkboxes et radios cross-browser (bonne réponse)
  • Il synchronise automatiquement les valeurs de formulaire avec un objet JavaScript
  • Il est utilisé pour sérialiser les formulaires en JSON
Explication : $.valHooks est un objet dont les clés sont des noms de tag ou types d'input (select, option, radio, checkbox). jQuery l'utilise en interne pour normaliser .val() sur les <select multiple> (retourne un tableau) et les checkboxes cross-browser. Exemple custom : $.valHooks['input'] = { get: function(el){ return el.value.trim(); }, set: function(el, val){ el.value = val; } }; — normalise automatiquement les espaces en début/fin pour tous les inputs. Cas réel : créer un hook pour un widget custom (ex: slider de prix) qui mappe .val() sur la valeur affichée par le widget.
Moins connu que $.cssHooks mais très utile pour les composants UI custom. Si vous créez un composant input custom (datepicker, colorpicker), implémentez un $.valHooks pour que .val() fonctionne nativement — cela améliore l'intégration avec $.serialize() et $.serializeArray().

  • Utiliser jQuery.noConflict() systématiquement
  • Utiliser un bundler (Webpack/Vite) avec un alias qui garantit que tout le code (applicatif + plugins) pointe vers la même instance jQuery (bonne réponse)
  • Charger jQuery deux fois avec des versions différentes via noConflict
  • Vérifier window.jQuery avant chaque opération
Explication : Deux instances jQuery dans la même page causent des problèmes subtils : les plugins attachés à une instance ne sont pas disponibles sur l'autre, les événements déclenchés via une instance ne sont pas captés par les handlers de l'autre. Avec Webpack, configurer : resolve: { alias: { jquery: require.resolve('jquery') } } et plugins: [new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', 'window.jQuery': 'jquery' })] garantit une instance unique. En environnement script tag classique : chaque fichier concatené doit utiliser la même variable globale window.jQuery. $.noConflict() sert à coexister avec d'autres bibliothèques utilisant $ (ex: Prototype.js), pas à gérer les instances multiples.
Diagnostic d'instance double : window.jQuery === $ dans la console doit retourner true. Si un plugin fait window.jQuery !== require('jquery') dans votre bundle, vous avez deux instances.

  • $(el).length > 0
  • $.contains(document.documentElement, el) ou $(el).closest('html').length > 0 (bonne réponse)
  • $(el).is(':attached')
  • $(el).parent().length > 0
Explication : $(el).length > 0 vérifie que l'objet jQuery contient des éléments, pas qu'ils sont dans le DOM. Un élément créé mais non inséré ($('<div>')) a une longueur de 1 mais n'est pas dans le DOM. $.contains(document.documentElement, el) vérifie si document.documentElement (le <html>) contient el comme descendant — précis et performant. Équivalent natif : document.documentElement.contains(el). $(el).closest('html').length > 0 fonctionne aussi mais traverse la chaîne des parents jusqu'à <html> — moins direct. En production, cette vérification est utile après un .detach() ou quand des callbacks asynchrones peuvent s'exécuter après suppression d'un élément.
Pattern défensif dans les callbacks AJAX : $.ajax(url).done(function(data){ if(!$.contains(document, )) return; // traitement... });. Évite les erreurs quand l'utilisateur navigue avant la fin de la requête dans une SPA.