querySelector vs getElementById : lequel choisir et pourquoi ?
Comparaison des méthodes de sélection DOM en JavaScript : querySelector, getElementById et getElementsByClassName. Différences de performance, cas d'usage et règles pratiques pour faire le bon choix.
Quand on débute en JavaScript, on apprend parfois querySelector comme méthode universelle pour récupérer des éléments du DOM. C’est pratique, flexible, et ça marche dans tous les cas. Mais ce n’est pas toujours le meilleur choix. Il existe des méthodes plus directes, plus rapides, et souvent plus lisibles selon le contexte.
Les méthodes en présence
Trois méthodes couvrent la majorité des cas :
document.getElementById('my-id'): sélectionne un élément par son attributiddocument.getElementsByClassName('my-class'): sélectionne tous les éléments ayant une classe donnéedocument.querySelector('#my-id')oudocument.querySelector('.my-class'): sélectionne le premier élément correspondant à n’importe quel sélecteur CSS
Pourquoi querySelector est plus lent
querySelector accepte n’importe quelle expression CSS valide. Cette flexibilité a un coût : le moteur doit parser la chaîne de sélecteur avant même de chercher l’élément dans le DOM. Pour getElementById, il n’y a rien à parser : le moteur cherche directement dans une table de hachage indexée par identifiant. C’est structurellement plus rapide.
// Le moteur doit parser '#submit-btn' avant de chercher
document.querySelector('#submit-btn')
// Accès direct, sans parsing
document.getElementById('submit-btn')
Parlons concret : les chiffres du benchmark
Voici les résultats mesurés sur un document DOM réel (benchmark disponible ici), du plus rapide au moins rapide :
| Méthode | Opérations/sec | Facteur |
|---|---|---|
getElementById |
11 800 223 | ×387 |
getElementsByClassName |
11 470 718 | ×376 |
querySelector('.class') |
7 814 403 | ×256 |
getElementsByClassName('class')[0] |
7 694 284 | ×252 |
querySelector('#id') |
49 241 | ×1,6 |
querySelectorAll('.class') |
30 514 | ×1 (référence) |
Le résultat le plus frappant : querySelector('#id') est 240 fois plus lent que getElementById. Les deux sélectionnent pourtant le même élément. La différence vient du parsing du sélecteur CSS '#id' que querySelector doit analyser avant de chercher, alors que getElementById accède directement à la table de hachage interne du navigateur.
Résultat inattendu : querySelector('.class') (7,8 M ops/sec) est bien plus rapide que querySelector('#id') (49 K ops/sec). Les moteurs de rendu modernes ont des chemins de traitement différents selon le type de sélecteur. Les sélecteurs de classe bénéficient d’une optimisation interne que les sélecteurs d’identifiant dans querySelector n’ont pas, ce qui explique cet écart surprenant au sein de la même méthode.
getElementById : le plus rapide pour les IDs
×240 d’écart pour un résultat identique. Les deux lignes ci-dessous font exactement la même chose :
// Accès direct à la table de hachage interne
const button = document.getElementById('submit-btn')
// Parsing du sélecteur, puis recherche
const button = document.querySelector('#submit-btn')
Les deux retournent un Element ou null. Comportement identique, performance très différente.
getElementsByClassName : rapide mais avec une subtilité
getElementsByClassName est plus rapide que querySelector('.my-class') pour la même raison que getElementById : pas de parsing de sélecteur CSS.
Mais il y a une différence importante à connaître : elle retourne une HTMLCollection vivante, pas un Element.
const items = document.getElementsByClassName('list-item') // HTMLCollection (live)
const first = document.querySelector('.list-item') // Element (ou null)
Une HTMLCollection vivante se met à jour automatiquement quand le DOM change. Si des éléments sont ajoutés ou supprimés, la collection reflète l’état courant sans qu’on ait à la recalculer. C’est utile dans certains contextes, mais ça peut aussi surprendre :
const items = document.getElementsByClassName('active')
console.log(items.length) // 3
items[0].classList.remove('active')
console.log(items.length) // 2 (l'élément a disparu de la collection)
querySelectorAll retourne à l’inverse une NodeList statique : une photographie du DOM au moment de l’appel, qui ne se met pas à jour.
Quand querySelector reste le bon choix
querySelector devient incontournable dès que le sélecteur est complexe : pseudo-classes, combinaisons, sélecteurs d’attributs…
// Impossible sans querySelector
document.querySelector('input[type="email"]:not([disabled])')
document.querySelector('.form > .field:first-child')
document.querySelector('[data-role="primary"]')
Pour ces cas, il n’existe pas d’alternative native plus rapide. querySelector est le bon outil.
La règle pratique
| Cas | Méthode à privilégier | Pourquoi |
|---|---|---|
Sélection par id |
getElementById |
×240 plus rapide que querySelector('#id') |
| Sélection par classe (premier élément) | querySelector('.class') |
×256 plus rapide que querySelectorAll |
| Sélection par classe (tous les éléments) | getElementsByClassName si la collection live est utile, querySelectorAll sinon |
×376 vs ×1 |
| Sélecteur complexe (attribut, pseudo-classe, combinaison) | querySelector ou querySelectorAll |
Pas d’alternative native |
En pratique, la différence de performance ne sera perceptible que si la sélection est répétée un grand nombre de fois (boucles serrées, animations, listes longues). Dans un handler de clic ou une initialisation de composant, l’écart est négligeable.
Le vrai bénéfice de getElementById est double : performance brute et lisibilité. Voir getElementById dans le code, c’est savoir immédiatement qu’on cible un élément unique identifié. C’est de la documentation implicite.
Bon dév, et bisou.
Keiro