Comment convertir une liste de nœuds DOM en tableau en Javascript?

96

J'ai une fonction Javascript qui accepte une liste de nœuds HTML, mais elle attend un tableau Javascript (il exécute certaines méthodes Array là-dessus) et je veux lui donner la sortie Document.getElementsByTagNamequi renvoie une liste de nœuds DOM.

Au départ, j'ai pensé à utiliser quelque chose de simple comme:

Array.prototype.slice.call(list,0)

Et cela fonctionne très bien dans tous les navigateurs, sauf bien sûr Internet Explorer qui renvoie l'erreur "Objet JScript attendu", car apparemment la liste des nœuds DOM renvoyée par les Document.getElement*méthodes n'est pas un objet JScript suffisant pour être la cible d'un appel de fonction.

Avertissements: cela ne me dérange pas d'écrire du code spécifique à Internet Explorer, mais je ne suis pas autorisé à utiliser des bibliothèques Javascript telles que JQuery car j'écris un widget à intégrer dans un site Web tiers et je ne peux pas charger de bibliothèques externes qui créera un conflit pour les clients.

Mon dernier effort consiste à parcourir la liste des nœuds DOM et à créer moi-même un tableau, mais y a-t-il une meilleure façon de le faire?

Guss
la source
Mieux encore, créez une fonction pour convertir à partir de la liste des nœuds DOM, mais ce serait vraiment ma solution, je pense que vous avez bien compris.
Kristoffer Sall-Storgaard
> for (i = 0; i & lt; x.length; i ++) Pourquoi obtenir la longueur de la NodeList à chaque itération? Ce n'est pas seulement une perte de temps, mais comme les NodeLists sont des collections en direct, si quelque chose dans le corps de la boucle change de longueur, vous pouvez boucler sans fin ou atteindre un index hors limites. Ce dernier est le pire qui puisse arriver si vous attribuez la longueur à une variable, et une erreur est bien meilleure qu'une boucle sans fin.
C'est une question très ancienne, mais jQuery a été construit avec la méthode .noConflict spécifiquement pour ne pas provoquer de conflit avec d'autres bibliothèques (même elle-même), ce qui signifie que plusieurs versions de jQuery pourraient être chargées sur une page. Cela dit, il vaut mieux éviter d'utiliser / charger une bibliothèque à moins que ce ne soit absolument nécessaire.
vol7ron
@ vol7ron: avance rapide jusqu'en 2016, et tout le monde est toujours au courant de la taille que les bibliothèques javascript ajoutent à la page. Certes, JQuery minifié et gzippé fait 30 Ko, c'est encore 30 Ko de trop juste pour transformer une liste de nœuds :-)
Guss

Réponses:

64

Les listes de nœuds sont des objets hôtes , l'utilisation de la Array.prototype.sliceméthode sur les objets hôtes n'est pas garantie de fonctionner, la spécification ECMAScript indique:

La question de savoir si la fonction de tranche peut être appliquée avec succès à un objet hôte dépend de l'implémentation.

Je vous recommande de créer une fonction simple pour parcourir le NodeListet ajouter chaque élément existant à un tableau:

function toArray(obj) {
  var array = [];
  // iterate backwards ensuring that length is an UInt32
  for (var i = obj.length >>> 0; i--;) { 
    array[i] = obj[i];
  }
  return array;
}

METTRE À JOUR:

Comme d'autres réponses le suggèrent, vous pouvez désormais utiliser dans les environnements modernes la syntaxe de diffusion ou la Array.fromméthode:

const array = [ ...nodeList ] // or Array.from(nodeList)

Mais en y réfléchissant, je suppose que le cas d'utilisation le plus courant pour convertir une NodeList en Array est de l'itérer, et maintenant l' NodeList.prototypeobjet a la forEachméthode nativement , donc si vous êtes dans un environnement moderne, vous pouvez l'utiliser directement, ou avoir un pollyfill.

Christian C. Salvadó
la source
2
Cela crée un tableau avec l'ordre d'origine de la liste inversé, ce qui, je suppose, n'est pas ce que veut l'OP. Vouliez-vous faire array[i] = obj[i]au lieu de array.push(obj[i])?
Tim Down
@Tim, d'accord, je l'avais comme ça avant mais édité hier soir sans m'en apercevoir (3h du matin heure locale :), merci !.
Christian C. Salvadó
9
Dans quelles circonstances serait obj.lengthautre chose qu'une valeur entière?
Peter
1
Je ne peux pas croire que ce soit si compliqué. Laid. C'est un besoin très courant dans la programmation Web / JS. Une nouvelle méthode pour la prochaine version du langage?
Andrew Koper
1
@AlbertoPerez, de rien !. Saludos hasta Madrid!
Christian C. Salvadó
126

Dans es6, vous pouvez simplement utiliser comme suit:

  • Opérateur de propagation

     var elements = [... nodelist]
  • En utilisant Array.from

     var elements = Array.from(nodelist)

plus de références sur https://developer.mozilla.org/en-US/docs/Web/API/NodeList

camiloazula
la source
4
si facile avec Array.from(): D
Josan Iracheta
4
au cas où quelqu'un utiliserait cette approche avec Typescript (vers ES5), cela ne Array.fromfonctionne que, car TS transpile cela vers nodelist.slice- ce qui n'est pas pris en charge.
Peter Albert
J'ai répondu la même chose un an avant toi et tu m'as passé les votes? Je ne peux pas expliquer cela ..
vsync
3
@vsync, votre réponse ne mentionne pasArray.from
ESR
@EdmundReed - alors? comment cela le justifie. c'est plus long à écrire donc en situation réelle, il ne sera jamais utilisé, seul spreadest utilisé.
vsync
16

En utilisant spread (ES2015) , c'est aussi simple que:[...document.querySelectorAll('p')]

(facultatif: utilisez Babel pour transpiler le code ES6 ci-dessus en syntaxe ES5)


Essayez-le dans la console de votre navigateur et voyez la magie:

for( links of [...document.links] )
  console.log(links);
vsync
la source
Au moins au dernier chrome, 44, j'obtiens ceci: Uncaught TypeError: document.querySelectorAll n'est pas une fonction (…)
Nick
@OmidHezaveh - Comme je l'ai dit, c'est du code ES6. Je ne sais pas si Chrome 44 prend en charge ES6 et si oui, à quelle couverture. C'est un navigateur vieux de presque un an et vous devrez évidemment exécuter ce code sur un navigateur prenant en charge la propagation ES6.
vsync
Ou transpilez-le en es5 avant l'exécution
HelloWorld
8

Utilisez cette astuce simple

<Your array> = [].map.call(<Your dom array>, function(el) {
    return el;
})
Gena Shumilkin
la source
Pouvez-vous expliquer pourquoi pensez-vous que cela a de meilleures chances de succès que l'utilisation Array.prototype.slice(ou [].slicecomme vous le dites)? À titre de note, j'aimerais faire remarquer que l'erreur spécifique à IE que j'ai documentée dans le Q se produit dans IE 8 ou une version inférieure, où elle mapn'est de toute façon pas implémentée. Dans IE 9 ( « mode standard ») ou plus, à la fois sliceet mapréussir de la même manière.
Guss
6

Bien que ce ne soit pas vraiment un shim approprié, puisqu'il n'y a pas de spécification nécessitant de travailler avec des éléments DOM, j'en ai créé une pour vous permettre de l'utiliser slice()de cette manière: https://gist.github.com/brettz9/6093105

MISE À JOUR : Quand j'ai soulevé cela avec l'éditeur de la spécification DOM4 (demandant s'ils pouvaient ajouter leurs propres restrictions aux objets hôtes (de sorte que la spécification obligerait les implémenteurs à convertir correctement ces objets lorsqu'ils sont utilisés avec des méthodes de tableau) au-delà de la spécification ECMAScript qui avait autorisé pour l'indépendance de l'implémentation), il a répondu que "les objets hôtes sont plus ou moins obsolètes par ES6 / IDL." Je vois par http://www.w3.org/TR/WebIDL/#es-array que les spécifications peuvent utiliser cet IDL pour définir des "objets de tableau de plate-forme" mais http://www.w3.org/TR/domcore/ ne le fait pas 'ne semble pas utiliser le nouvel IDL pour (qui hérite de Array.prototype) est utilisé pour (et est maintenant obsolète au profit du seul élément qui l'utilise encore,HTMLCollection (bien qu'il semble que cela puisse être le cas car Element.attributesil déclare seulement explicitement qu'il utilise WebIDL pour DOMString et DOMTimeStamp). Je vois[ArrayClass]NodeListNamedNodeMapElement.attributes ). Dans tous les cas, on dirait que ça va devenir standard. L'ES6 Array.frompourrait également être plus pratique pour de telles conversions que d'avoir à spécifier Array.prototype.sliceet plus sémantiquement clair que [].slice()(et la forme plus courte, Array.slice()(un "tableau générique"), n'est pas devenue, pour autant que je sache, un comportement standard).

Brett Zamir
la source
J'ai mis à jour pour indiquer que les spécifications peuvent évoluer dans le sens de ce comportement.
Brett Zamir
5

Aujourd'hui, en 2018, nous pourrions utiliser l'ECMAScript 2015 (6e édition) ou l'ES6, mais tous les navigateurs ne peuvent pas le comprendre (par exemple, IE ne comprend pas tout). Si vous le souhaitez, vous pouvez utiliser ES6 comme suit: var array = [... NodeList];( comme opérateur de diffusion ) ou var array = Array.from(NodeList);.

Dans les autres cas (si vous ne pouvez pas utiliser ES6), vous pouvez utiliser le moyen le plus court pour convertir un NodeListen Array:

var array = [].slice.call(NodeList, 0);.

Par exemple:

var nodeList = document.querySelectorAll('input');
//we use "{}.toString.call(Object).slice(8, -1)" to find the class name of object
console.log({}.toString.call(nodeList).slice(8, -1)); //NodeList

var array = [].slice.call(nodeList, 0);
console.log({}.toString.call(array).slice(8, -1)); //Array

var result = array.filter(function(item){return item.value.length > 5});

for(var i in result)
  console.log(result[i].value); //credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

Mais si vous voulez parcourir la DOMliste des nœuds facile que, vous n'avez pas besoin de convertir un NodeListà un Array. Il est possible de parcourir les éléments en NodeListutilisant:

var nodeList = document.querySelectorAll('input');
// Calling nodeList.item(i) isn't necessary in JavaScript
for(var i = 0; i < nodeList.length; i++)
    console.log(nodeList[i].value); //trust, credit, confidence
<input type="text" value="trust"><br><br>
<input type="text" value="credit"><br><br>
<input type="text" value="confidence">

Ne soyez pas tenté d'utiliser for...inou for each...ind'énumérer les éléments de la liste, car cela énumérera également la longueur et les propriétés de l'élément NodeListet provoquera des erreurs si votre script suppose qu'il ne doit traiter que des objets élément. En outre, il for..inn'est pas garanti de visiter les propriétés dans un ordre particulier. for...ofles boucles boucleront correctement sur les objets NodeList.

Voir aussi:

Bharata
la source
3
var arr = new Array();
var x= ... get your nodes;

for (i=0;i<x.length;i++)
{
  if (x.item(i).nodeType==1)
  {
    arr.push(x.item(i));
  }
}

Cela devrait fonctionner, traverser le navigateur et obtenir tous les nœuds "élément".

Strelok
la source
1
C'est fondamentalement la même chose que la réponse de @ CMS, sauf que cela suppose que je ne veux que des nœuds d'élément - ce que je ne veux pas.
Guss