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 attribut id
  • document.getElementsByClassName('my-class') : sélectionne tous les éléments ayant une classe donnée
  • document.querySelector('#my-id') ou document.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

Lire aussi