Jak przekonwertować listę węzłów DOM na tablicę w JavaScript?

101

Mam funkcję Javascript, która akceptuje listę węzłów HTML, ale oczekuje tablicy Javascript (uruchamia na niej niektóre metody Array) i chcę przekazać jej wynik, Document.getElementsByTagNamektóry zwraca listę węzłów DOM.

Początkowo myślałem o użyciu czegoś prostego, takiego jak:

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

I to działa dobrze we wszystkich przeglądarkach, z wyjątkiem oczywiście Internet Explorera, który zwraca błąd „Oczekiwano obiektu JScript”, ponieważ najwyraźniej lista węzłów DOM zwrócona przez Document.getElement*metody nie jest wystarczającym obiektem JScript, aby być celem wywołania funkcji.

Ostrzeżenia: nie mam nic przeciwko pisaniu kodu specyficznego dla przeglądarki Internet Explorer, ale nie mogę używać żadnych bibliotek JavaScript, takich jak JQuery, ponieważ piszę widżet do osadzenia w witrynie internetowej innej firmy i nie mogę załadować bibliotek zewnętrznych, które stworzy konflikt dla klientów.

Ostatnim moim wysiłkiem jest iteracja listy węzłów DOM i samodzielne utworzenie tablicy, ale czy istnieje lepszy sposób na zrobienie tego?

Guss
źródło
Jeszcze lepiej, utwórz funkcję do konwersji z listy węzłów DOM, ale to naprawdę byłoby moje rozwiązanie, myślę, że masz rację.
Kristoffer Sall-Storgaard
> for (i = 0; i & lt; x.length; i ++) Po co pobierać długość NodeList w każdej iteracji? To nie tylko strata czasu, ale ponieważ NodeLists to kolekcje na żywo, jeśli cokolwiek w treści pętli zmieni swoją długość, możesz zapętlić się w nieskończoność lub trafić indeks poza granicami. To ostatnie jest najgorszym, co może się zdarzyć, jeśli przypiszesz długość do zmiennej, a błąd jest znacznie lepszy niż nieskończona pętla.
To jest naprawdę stare pytanie, ale jQuery zostało zbudowane przy użyciu metody .noConflict specjalnie, aby nie powodować konfliktów z innymi bibliotekami (nawet sobą), co oznacza, że ​​na stronie można załadować wiele wersji jQuery. To powiedziawszy, najlepiej unikać używania / ładowania biblioteki, chyba że absolutnie musisz.
vol7ron
@ vol7ron: przewijamy do 2016 roku i wszyscy wciąż są przekonani o rozmiarze, jaki biblioteki javascript dodają do strony. To prawda, JQuery zminimalizowany i spakowany na gzipie ma 30 KB, wciąż jest to 30 KB za dużo, aby przekształcić listę węzłów :-)
Guss

Odpowiedzi:

67

NodeLists są obiektami hosta , Array.prototype.slicenie ma gwarancji , że metoda na obiektach hosta będzie działać, specyfikacja ECMAScript stwierdza:

To, czy funkcja plastra może zostać pomyślnie zastosowana do obiektu hosta, zależy od implementacji.

Zalecałbym wykonanie prostej funkcji do iteracji po NodeListi dodania każdego istniejącego elementu do tablicy:

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;
}

AKTUALIZACJA:

Jak sugerują inne odpowiedzi, możesz teraz używać w nowoczesnych środowiskach składni rozprzestrzeniania lub Array.frommetody:

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

Ale myśląc o tym, myślę, że najczęstszym przypadkiem użycia konwersji NodeList na Array jest iteracja po nim, a teraz NodeList.prototypeobiekt ma forEachnatywną metodę , więc jeśli jesteś w nowoczesnym środowisku, możesz go użyć bezpośrednio lub mieć pollyfill.

Christian C. Salvadó
źródło
2
To jest tworzenie tablicy z odwróconą pierwotną kolejnością listy, co, jak sądzę, nie jest tym, czego chce OP. Czy chciałeś array[i] = obj[i]zamiast tego zrobić array.push(obj[i])?
Tim Down
@Tim, prawda, miałem to już wcześniej, ale edytowałem wczoraj wieczorem, nie zauważając tego (3 nad ranem czasu lokalnego :), dzięki !.
Christian C. Salvadó
9
W jakich okolicznościach byłoby obj.lengthto coś innego niż liczba całkowita?
Peter
1
Nie mogę uwierzyć, że to takie skomplikowane. Brzydki. To bardzo powszechna potrzeba w programowaniu Web / JS. Nowa metoda na kolejne wydanie języka?
Andrew Koper
1
@AlbertoPerez, nie ma za co !. Saludos hasta Madrid!
Christian C. Salvadó
131

W es6 możesz po prostu użyć w następujący sposób:

  • Operator rozprzestrzeniania

     var elements = [... nodelist]
    
  • Za pomocą Array.from

     var elements = Array.from(nodelist)
    

więcej informacji na https://developer.mozilla.org/en-US/docs/Web/API/NodeList

camiloazula
źródło
4
tak łatwo z Array.from(): D
Josan Iracheta
4
w przypadku, gdy ktoś używa tego podejścia z Typescript (do ES5), Array.fromdziała tylko , ponieważ TS transponuje to na nodelist.slice- co nie jest obsługiwane.
Peter Albert,
Odpowiedziałem to samo rok wcześniej i zdałeś mnie w głosowaniu? Nie potrafię tego wyjaśnić ...
vsync
3
@vsync, w twojej odpowiedzi nie ma wzmiankiArray.from
ESR
@EdmundReed - tak? jak to usprawiedliwia. pisanie jest dłuższe, więc w rzeczywistości nigdy nie zostanie użyte, tylko spreadzostanie użyte.
vsync
17

Korzystanie ze spreadu (ES2015) jest tak proste, jak:[...document.querySelectorAll('p')]

(opcjonalnie: użyj Babel, aby przetransponować powyższy kod ES6 do składni ES5)


Wypróbuj w konsoli przeglądarki i zobacz magię:

for( links of [...document.links] )
  console.log(links);
vsync
źródło
Przynajmniej ostatni chrome, 44, dostaję to: Uncaught TypeError: document.querySelectorAll nie jest funkcją (…)
Nick
@OmidHezaveh - Jak powiedziałem, to jest kod ES6. Nie wiem, czy Chrome 44 obsługuje ES6, a jeśli tak, to w jakim zakresie. To prawie roczna przeglądarka i oczywiście musiałbyś uruchomić ten kod na przeglądarce obsługującej rozprzestrzenianie się ES6.
vsync
Lub przetransponuj go do es5 przed wykonaniem
HelloWorld
8

Użyj tej prostej sztuczki

<Your array> = [].map.call(<Your dom array>, function(el) {
    return el;
})
Gena Shumilkin
źródło
Czy możesz wyjaśnić, dlaczego uważasz, że ma to większą szansę na sukces niż użycie Array.prototype.slice(lub [].slicejak to ujęłeś)? Na marginesie chciałbym skomentować, że błąd specyficzny dla IE, który udokumentowałem w Q, występuje w IE 8 lub starszym, gdzie i maptak nie jest zaimplementowany. W IE 9 („tryb standardów”) lub nowszym oba slicei działają mapw ten sam sposób.
Guss
6

Chociaż nie jest to właściwa podkładka, ponieważ nie ma specyfikacji wymagającej pracy z elementami DOM, stworzyłem taką, aby umożliwić Ci użycie slice()w ten sposób: https://gist.github.com/brettz9/6093105

AKTUALIZACJA : Kiedy podniosłem to z edytorem specyfikacji DOM4 (pytając, czy mogą dodać własne ograniczenia do obiektów hosta (tak, aby specyfikacja wymagała od implementatorów poprawnej konwersji tych obiektów, gdy są używane z metodami tablicowymi) poza specyfikację ECMAScript, która miała dopuszcza niezależność implementacji), odpowiedział, że „obiekty hosta są mniej więcej przestarzałe według ES6 / IDL”. Widzę na http://www.w3.org/TR/WebIDL/#es-array, że specyfikacje mogą używać tego IDL do definiowania „obiektów tablicy platformy”, ale http://www.w3.org/TR/domcore/ nie Wydaje się, że nie używa nowego IDL dla HTMLCollection(chociaż wygląda na to, że może to robić, Element.attributeschociaż wyraźnie stwierdza, że ​​używa WebIDL dla DOMString i DOMTimeStamp). Rozumiem[ArrayClass](który dziedziczy po Array.prototype) jest używany do NodeList(i NamedNodeMapjest obecnie przestarzały na rzecz jedynego elementu, który nadal by go używał Element.attributes). W każdym razie wygląda na to, że ma stać się standardem. ES6 Array.frommoże być również wygodniejszy dla takich konwersji niż konieczność określenia Array.prototype.slicei bardziej przejrzysty semantycznie niż [].slice()(a krótsza forma Array.slice()(„rodzaj tablicy”), o ile wiem, nie stała się standardowym zachowaniem).

Brett Zamir
źródło
Zaktualizowałem, aby wskazać, że specyfikacje mogą zmierzać w kierunku wymagania takiego zachowania.
Brett Zamir
5

Dziś, w 2018 roku, moglibyśmy skorzystać z ECMAScript 2015 (6. edycja) lub ES6, ale nie wszystkie przeglądarki go rozumieją (np. IE nie rozumie go w całości). Jeśli chcesz, możesz użyć ES6 w następujący sposób: var array = [... NodeList];( jako operator spreadu ) lub var array = Array.from(NodeList);.

W innym przypadku (jeśli nie możesz użyć ES6) możesz użyć najkrótszej drogi do konwersji a NodeListna Array:

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

Na przykład:

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">

Ale jeśli chcesz DOMłatwo iterować po liście węzłów, nie musisz konwertować a NodeListna Array. Możliwe jest zapętlenie elementów w NodeListusing:

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">

Nie ulegaj pokusie for...in lub for each...inwyliczania elementów na liście, ponieważ spowoduje to również wyliczenie długości i właściwości elementu NodeListoraz spowoduje błędy, jeśli twój skrypt zakłada, że ​​ma do czynienia tylko z obiektami elementów. Nie for..inma również gwarancji, że odwiedzisz nieruchomości w określonej kolejności. for...ofpętle będą poprawnie zapętlać obiekty NodeList.

Zobacz też:

Bharata
źródło
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));
  }
}

Powinno to zadziałać, przejść przez przeglądarkę i uzyskać wszystkie węzły „elementów”.

Strelok
źródło
1
Jest to w zasadzie to samo, co odpowiedź @ CMS, z tym wyjątkiem, że zakłada, że ​​chcę tylko węzłów elementów - czego nie chcę.
Guss,