Niveau Niveau confirmé

DOM : querySelector et querySelectorAll

Articlejavascript

Publié par le , mis à jour le (118301 lectures)

javascript dom queryselectorall queryselector getElementById

L'API Selectors définit des méthodes pour accéder aux noeuds DOM, c'est-à-dire aux éléments HTML présents, à l'aide de fonctions de recherche. Leur intérêt est de faciliter la manipulation des noeuds en JavaScript, et d'aller bien au-delà des quelques fonctions qui équipaient les premières versions de l'interface DOM :

  • getElementById() : recherche un élément d'après son identifiant (attribut id),
  • getElementsByTagName() : recherche des éléments d'après leur type (balise),
  • getElementsByClassName() : recherche des éléments d'après leur classe (attribut class)

Ces 3 méthodes sont performantes mais limitées. Avec des pages ou applications complexes, elles se révèlent peu souples car nécessitent de faire appel à des boucles, diverses opérations algorithmiques et des filtres sur les résultats obtenus pour pouvoir obtenir une liste d'éléments.

Jumelles

Avec deux nouvelles fonctions introduites par l'API Selectors, la syntaxe permet de faire appel aux sélecteurs CSS. On applique en général ces deux méthodes à partir de la racine document.

  • querySelector() : retourne le premier élément trouvé satisfaisant au sélecteur (type de retour : Element), ou null si aucun objet correspondant n'est trouvé.
  • querySelectorAll() : retourne tous les éléments satisfaisant au sélecteur, dans l'ordre dans lequel ils apparaissent dans l'arbre du document (type de retour : NodeList), ou un tableau NodeList vide si rien n'est trouvé.

Brève comparaison

Les exemples suivants cherchent toutes les cellules <td> contenues dans un élément <table id="matable">.

// Version DOM native
var table = document.getElementById("matable");
var tbodies = table.tBodies;
var rangees = null;
var cellules = [];

for (var i = 0; i < tbodies.length; i++) {
  rangees = tbodies[i].rows;
  for (var j = 0; j < rangees.length; j++) {
    cellules.push(rangees[j].cells[1]);
  }
}

Il faut une dizaine de lignes de code pour pouvoir obtenir un tableau de cellules, ce qui est dissuasif et chronophage.

La popularité des frameworks JavaScript tels que jQuery équipé de Sizzle est initialement venue de ce manque de simplicité au niveau natif : ils permettent de rechercher des éléments HTML sur la page à l'aide d'une seule instruction, et de surcroît avec la syntaxe CSS plus compacte et lisible, en effectuant en interne tous les parcours pour arriver au bout de la recherche dans le document.

// Version jQuery ou Sizzle
var cellules = $('#matable td');

Les deux inconvénients avec le passage par jQuery sont de devoir charger la bibliothèque (poids du fichier au téléchargement, requête HTTP supplémentaire, et temps d'interprétation) ainsi que de faire appel à des instructions en JavaScript non natives, moins performantes que des instructions directement codées dans le navigateur.
Remarque : le type des données obtenues avec jQuery est différent, il s'agit d'un tableau d'objets jQuery qui sont prévus pour utiliser les autres méthodes du framework, et non d'une simple liste de noeuds DOM. On peut néanmoins retomber sur ce niveau inférieur avec la méthode .get().

C'est pour obtenir ce type de fonctionnement au niveau natif que l'API Selectors fut mise au point.

// Version API Selectors
var cellules = document.querySelectorAll('#matable td');

Ses avantages sont d'être très performante (voir le benchmark en ligne SlickSpeed comparant les méthodes natives et les frameworks JavaScript) et disponible sans charger de fichier JavaScript supplémentaire... mais elle n'est pas supportée par les anciens navigateurs (notamment IE6 et IE7, voir tableau de support).

Exemples variés

Pousser le principe très loin et alors aisé, la seule limite étant la connaissance des sélecteurs CSS par le navigateur.

var cellules = document.querySelectorAll("#matable>tbody>tr>td:nth-of-type(2)");
var premier = document.querySelector("p:first-child");
// ...

De la même façon que l'on peut sélectionner plusieurs éléments séparés par des virgules dans une feuille de styles, le principe est applicable à l'API Selectors. Sans cela, ce même résultat nécessiterait de faire appel au moins deux fois à getElementsByClassName.

var paragraphes = document.querySelectorAll("p.intro, p.suite");

Si plusieurs possibilités sont passées de la sorte à querySelector() - qui est donc limitée à retourner un seul résultat - alors seul le premier élément satisfaisant l'une des conditions est retenu. L'ordre de l'énumération n'a pas d'importance, le critère de choix se base uniquement sur l'ordre d'apparition dans le DOM HTML.

var paragraphe = document.querySelector("p.intro, p.suite");

Dans ce cas de figure, seul un élément p.intro ou p.suite sera retourné, en réalité le premier qui sera rencontré dans le code source, quel que soit l'ordre de l'énumération passée à querySelector().

Pour utiliser le jeu d'éléments retourné par querySelectorAll(), il est bien souvent nécessaire d'itérer sur la liste à l'aide d'une boucle, démarrant à l'index 0 et aboutissant à la longueur du tableau définie par la propriété .length.

var cellules = document.querySelectorAll("#matable td");
for (var i = 0; i < cellules.length; i++) {
 // Faire quelque chose avec cellules.item(i) ou cellules[i]
}

Les méthodes querySelector() et querySelectorAll() ne sont pas vouées à être appliquées uniquement au document. Elles sont exploitables sur un noeud DOM, mais seront toujours évaluées dans le contexte général du document.

var matable = document.getElementById("matable");
var paragraphes = matable.querySelectorAll("p");

Que peut-on en faire ?

Une fois les noeuds sélectionnés dans le document, toutes les instructions ECMAScript/JavaScript sont applicables, par exemple...

Affecter un style particulier, de manière dynamique :

var importants = document.querySelectorAll('.important');
for (var i = 0; i < importants.length; i++) {
  importants[i].style.backgroundColor = 'yellow';
}

Masquer/afficher un élément :

var monmessage = document.querySelector('#message');
monmessage.style.display = 'block'; // Afficher
// ...
monmessage.style.display = 'none'; // Masquer

Affecter un gestionnaire d'événements au clic sur un élément pour en afficher un autre :

var monbouton = document.querySelector('#afficherMessage');
monbouton.onclick = function() {
  document.querySelector('#message').style.display = 'block';
}

Et ainsi de suite.

Tableau des compatibilités

Navigateurs Versions
Internet Explorer Internet Explorer 8+
Firefox Firefox 3.5+
Chrome Chrome 4+
Safari Safari 3.1+
Safari iOS Mobile 3.2
Opera Opera 10+
Opera Mini 5+
Opera Mobile 10+
Android Android 2.1+

Avertissements

Étant donné que ces méthodes de recherche exploitent directement le moteur du navigateur, elles sont limitées à ce qu'il reconnaît nativement en termes de sélecteurs. Par exemple il s'agira de CSS 2.1 sur IE8, et uniquement en mode conforme aux standards. Par ailleurs, ce navigateur ne retournera pas les éléments visés par les pseudo-classes :visited ou :link pour des raisons de sécurité. Il serait en effet possible de savoir en JavaScript si un visiteur est allé sur une adresse précise en sélectionnant des liens concernés par ces filtres et en étudiant leurs couleurs. Les autres navigateurs devront traiter tous les liens de la page comme non visités pour ne pas se trahir.

Commentaires

Ok pour les selecteurs, ça nous simplifie bien la vie, mais pourquoi n'y a t-il pas de méthode setattributes ? Comme ceci :
dom.setAttributes({
'id' : 'value',
'class' : 'value',
...................
});

C'est assez contraignant les setattribute, vous ne trouvez pas ?

Bonjour,

Merci pour cet article, cependant, j'ai du mal à comprendre le premier exemple.
Je cite :

"Les exemples suivants cherchent toutes les cellules <td> contenues dans un élément <table id="matable">."

Pourquoi ne pas utiliser :

var table = document.getElementById("maTable"),
tableCells = table.getElementsByTagName('td');

tableCells correspondra bien à la "collection" de toutes les balises td.

A vous lire...

Salut,
"Ces deux méthodes retournent null si aucun objet correspondant n'est trouvé."
Nope, querySelectorAll retourne une NodeList vide.

@yannouche_b : pas faux, mais que crois que le fail vient du fait que Dew voulait un exemple un peu plus complexe en introduisant les tbodies, mais que le query selector est limité à cause des tbodies implicites (du moins je présume). Il faudrait ajouter un code HTML à l'exemple avec des sections de table explicites, et modifier le query selector en "#matable tbody td".

"Les deux inconvénients avec le passage par jQuery sont de devoir charger la bibliothèque [...] ainsi que de **faire appel à des instructions en JavaScript non natives, moins performantes que des instructions directement codées dans le navigateur**.

Cette deuxième partie sur les performances est incorrecte : sous le capot jQuery utilise évidemment querySelectorAll dans les navigateurs qui l'implémentent. Il y a toujours un léger surcoût à utiliser cette fonction par l'API de jQuery plutôt que directement, mais il peut être considéré comme négligeable.

Louis-Rémi

Au niveau des performances je vous laisse les mesurer. Passer par jQuery est forcément pénalisant puisqu'il s'agit de passer par des wrappers qui feront appel selon les capacités du navigateur à des fonctions natives ou à Sizzle. Le coût en chargement et en exécution est toujours plus élevé, fut-il négligeable lorsqu'il s'agit de quelques millisecondes.

@yannouche_b Le jsPerf cité en exemple est pour le moins idiot, dans la mesure ou querySelectorAll sert à faire des requêtes complexes dans le DOM, et non pas remplacer toutes les autres méthodes. Comparons ce qui est comparable.

@Florian_R, Certes, les QS permettent de faire des requêtes complexes, mais une bonne partie des exemples cités dans cet article ( et dans beaucoup d'autres ressources ) utilisent des sélecteurs simples ( ex : var monbouton = document.querySelector('#afficherMessage'); )

La confusion est donc bien présente et la comparaison ( à sélecteur équivalent ) ne me semble pas si absurde.

var cellules = document.querySelectorAll("#matable td"); dans un document Html5 donne "TypeError: document.QuerySelectorAll is not a function" avec Chrome Version 79.0.3945.117