JS: iterowanie po wyniku getElementsByClassName przy użyciu Array.forEach

240

Chcę iterować niektóre elementy DOM, robię to:

document.getElementsByClassName( "myclass" ).forEach( function(element, index, array) {
  //do stuff
});

ale pojawia się błąd:

document.getElementsByClassName („moja_klasa”). forEach nie jest funkcją

Używam Firefoksa 3, więc wiem, że oba getElementsByClassNamei Array.forEachsą obecne. Działa to dobrze:

[2, 5, 9].forEach( function(element, index, array) {
  //do stuff
});

Czy wynika z getElementsByClassNametablicy? Jeśli nie, co to jest?

Steve Claridge
źródło

Odpowiedzi:

384

Nie. Jak podano w DOM4 , jest to HTMLCollection(przynajmniej w nowoczesnych przeglądarkach. Starsze przeglądarki zwróciły a NodeList).

We wszystkich współczesnych przeglądarkach (prawie wszystko inne IE <= 8) możesz wywołać forEachmetodę Array , przekazując jej listę elementów ( HTMLCollectionczy to lub NodeList) jako thiswartość:

var els = document.getElementsByClassName("myclass");

Array.prototype.forEach.call(els, function(el) {
    // Do stuff here
    console.log(el.tagName);
});

// Or
[].forEach.call(els, function (el) {...});

Jeśli jesteś szczęśliwy, że możesz korzystać z ES6 (tzn. Możesz bezpiecznie zignorować Internet Explorera lub używasz transpilatora ES5), możesz użyć Array.from:

Array.from(els).forEach((el) => {
    // Do stuff here
    console.log(el.tagName);
});
Tim Down
źródło
29
Nie trzeba najpierw konwertować go na macierz. Po prostu użyj [].forEach.call(elsArray, function () {...}).
kay - SE is evil
1
To NIE jest lista węzłów. Jest to obiekt podobny do tablicy. Nawet nie sądzę, że ma typ instancji. querySelectorAllMetoda zwraca jednak NodeList.
Maksim Vi.
2
@MaksimVi. Masz całkowitą rację: DOM4 określa, że document.getElementsByClassName()powinien zwrócić HTMLCollection(który jest bardzo podobny, ale nie NodeList). Dzięki za zwrócenie uwagi na błąd.
Tim Down
@MaksimVi .: Zastanawiam się, czy to się kiedyś zmieniło. Zazwyczaj sprawdzam te rzeczy.
Tim Down
1
@TimDown, dzięki za HTMLCollectionwskazówkę. Teraz w końcu mogę użyć HTMLCollection.prototype.forEach = Array.prototype.forEach;w moim kodzie.
Maksim Vi.
70

Możesz użyć Array.fromdo konwersji kolekcji na tablicę, co jest o wiele czystsze niż Array.prototype.forEach.call:

Array.from(document.getElementsByClassName("myclass")).forEach(
    function(element, index, array) {
        // do stuff
    }
);

W starszych przeglądarkach, które nie obsługują Array.from, musisz użyć czegoś takiego jak Babel.


ES6 dodaje również tę składnię:

[...document.getElementsByClassName("myclass")].forEach(
    (element, index, array) => {
        // do stuff
    }
);

Restrukturyzacja spoczynkowa ...działa na wszystkich obiektach podobnych do tablicy, nie tylko na samych tablicach, a następnie do tworzenia tablicy na podstawie wartości używana jest stara, dobra składnia tablicy.


Podczas gdy funkcja alternatywna querySelectorAll(która sprawia, że ​​jest getElementsByClassNameprzestarzała) zwraca kolekcję, która ma forEachnatywnie, innych metod takich jak maplub filterbrak, więc ta składnia jest nadal przydatna:

[...document.querySelectorAll(".myclass")].map(
    (element, index, array) => {
        // do stuff
    }
);

[...document.querySelectorAll(".myclass")].map(element => element.innerHTML);
Athari
źródło
6
Uwaga: bez transpilacji zgodnie z sugestią (Babel), NIE jest to kompatybilne z IE <Edge, Opera, Safari <9, przeglądarka Android, Chrome na Androida, ... itd.) Źródło: mozilla dev docs
Sean
30

Lub możesz użyć, querySelectorAllktóra zwraca NodeList :

document.querySelectorAll('.myclass').forEach(...)

Obsługiwane przez nowoczesne przeglądarki (w tym Edge, ale nie IE):
Czy mogę używać querySelectorAll
NodeList.prototype.forEach ()

MDN: Document.querySelectorAll ()

icl7126
źródło
4
Należy pamiętać o ograniczeniu wydajności w stosunku do getElementByClassName
Szabolcs Páll
3
Kara za wydajność jest znikoma w porównaniu z innymi bardziej intensywnymi zadaniami, takimi jak modyfikowanie DOM. Jeśli wykonam 60 000 z nich w ciągu 1 milisekundy , jestem prawie pewien, że nie będzie to problemem dla jakiegokolwiek rozsądnego użycia :)
icl7126
1
Podłączyłeś zły test porównawczy. Oto poprawna miara: netthmark.net/Benchmarks/Show/4076/0/... Właśnie uruchomiłem to na moim słabszym telefonie, mam 160k / s vs 380k / s. Odkąd wspominałeś o manipulacji DOM, tutaj jest to, że zbyt pomiaruthat.net/Benchmarks/Show/5705/0/... Masz 50k / s vs. 130k / s. Jak widać, manipulowanie DOM jest jeszcze wolniejsze, prawdopodobnie z powodu statyczności NodeList (jak wspominają inni). Nadal nieistotne w większości przypadków użycia, ale mimo to prawie 3-krotnie wolniejsze.
Szabolcs Páll
14

Edycja: Mimo że typ zwrotu zmienił się w nowych wersjach HTML (patrz zaktualizowana odpowiedź Tim Down), poniższy kod nadal działa.

Jak powiedzieli inni, jest to lista NodeList. Oto kompletny, działający przykład, który możesz wypróbować:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <script>
            function findTheOddOnes()
            {
                var theOddOnes = document.getElementsByClassName("odd");
                for(var i=0; i<theOddOnes.length; i++)
                {
                    alert(theOddOnes[i].innerHTML);
                }
            }
        </script>
    </head>
    <body>
        <h1>getElementsByClassName Test</h1>
        <p class="odd">This is an odd para.</p>
        <p>This is an even para.</p>
        <p class="odd">This one is also odd.</p>
        <p>This one is not odd.</p>
        <form>
            <input type="button" value="Find the odd ones..." onclick="findTheOddOnes()">
        </form>
    </body>
</html>

Działa to w IE 9, FF 5, Safari 5 i Chrome 12 w Win 7.

james.garriss
źródło
9

Wynikiem getElementsByClassName()nie jest tablica, ale obiekt podobny do tablicy . W szczególności jest to nazywane an HTMLCollection, z którym nie należy mylić NodeList( który ma własną forEach()metodę ).

Jednym prostym sposobem przy użyciu ES2015 do konwersji obiektu typu tablicowego do użytku z Array.prototype.forEach()tym, o którym jeszcze nie wspomniano, jest użycie operatora lub składni spreadu :

const elementsArray = document.getElementsByClassName('myclass');

[...elementsArray].forEach((element, index, array) => {
    // do something
});
Kloptikus
źródło
2
Uważam, że jest to naprawdę właściwy sposób, aby to zrobić w nowoczesnych przeglądarkach. To jest dokładnie ta składnia rozkładów przypadków użycia, która została stworzona do rozwiązania.
Matt Korostoff,
3

Jak już powiedziano, getElementsByClassNamezwraca HTMLCollection , który jest zdefiniowany jako

[Exposed=Window]
interface HTMLCollection {
  readonly attribute unsigned long length;
  getter Element? item(unsigned long index);
  getter Element? namedItem(DOMString name);
};

Wcześniej niektóre przeglądarki zwracały zamiast tego NodeList .

[Exposed=Window]
interface NodeList {
  getter Node? item(unsigned long index);
  readonly attribute unsigned long length;
  iterable<Node>;
};

Różnica jest ważna, ponieważ DOM4 definiuje teraz NodeList s jako iterowalny.

Zgodnie z projektem Web IDL ,

Obiekty implementujące interfejs zadeklarowany jako obsługujący iterację są iterowane w celu uzyskania sekwencji wartości.

Uwaga : W powiązaniu języka ECMAScript iterowalny interfejs będzie miał „wpisy”, „forEach”, „klucze”, „wartości” i właściwości iteratora @@ na swoim prototypowym obiekcie interfejsu .

Oznacza to, że jeśli chcesz użyć forEach, możesz użyć metody DOM, która zwraca NodeList , jak querySelectorAll.

document.querySelectorAll(".myclass").forEach(function(element, index, array) {
  // do stuff
});

Uwaga: nie jest to jeszcze szeroko obsługiwane. Zobacz także metodę forEach dla Node.childNodes?

Oriol
źródło
1
Powrót do Chrome 49forEach in not a function
Witalij Zdanewicz
@VitalyZdanevich Wypróbuj Chromium 50
Oriol
W Chrome 50 dostajędocument.querySelectorAll(...).forEach is not a function
Witalij Zdanevich
@VitalyZdanevich To działało na Chromium 50 i nadal działa na Chromium 53. Może nie było uważane za wystarczająco stabilne, aby zostać wysłane do Chrome 50.
Oriol
1

Nie Arrayzwraca ani zwraca NodeList .

reko_t
źródło
1

Jest to bezpieczniejszy sposób:

var elements = document.getElementsByClassName("myclass");
for (var i = 0; i < elements.length; i++) myFunction(elements[i]);
gildniy
źródło
0

getElementsByClassNamezwraca HTMLCollection w nowoczesnych przeglądarkach.

który jest obiektem podobnym do tablicy podobnym do argumentów, które można iterować przez for...ofpętlę, zobacz poniżej, co mówi o tym MDN doc:

Instrukcja for ... tworzy pętlę iterującą po obiektach iterowalnych , w tym: wbudowanym String, Array, obiektach podobnych do Array (np. Argumenty lub NodeList), TypedArray, Map, Set i iterable zdefiniowane przez użytkownika. Wywołuje niestandardowy hak iteracji z instrukcjami do wykonania dla wartości każdej odrębnej właściwości obiektu.

przykład

for (let element of getElementsByClassName("classname")){
   element.style.display="none";
}
Haritsinh Gohil
źródło
Nie tak, zgodnie z maszynowym pismem:error TS2488: Type 'HTMLCollectionOf<Element>' must have a '[Symbol.iterator]()' method that returns an iterator.
Turtles Are Cute
@TurtlesAreCute, tutaj OP używa javascript a nie maszynopisu i odpowiedziałem zgodnie z zaleceniem waniliowym js, więc w maszynopisie może być inne rozwiązanie problemu.
Haritsinh Gohil
@TurtlesAreCute, Nawiasem mówiąc, działa również również w maszynopisie, ale musisz wspomnieć o właściwym typie zmiennej, która zawiera element konkretnej klasy css, więc może ją odpowiednio rzutować, po szczegóły zobacz tę odpowiedź .
Haritsinh Gohil
0

Oto test, który utworzyłem na jsperf: https://jsperf.com/vanillajs-loop-through-elements-of-class

Najbardziej udoskonaloną wersją w Chrome i Firefox jest stara dobra pętla for w połączeniu z document.getElementsByClassName:

var elements = document.getElementsByClassName('testClass'), elLength = elements.length;
for (var i = 0; i < elLength; i++) {
    elements.item(i).textContent = 'Tested';
};

W Safari ten wariant jest zwycięzcą:

var elements = document.querySelectorAll('.testClass');
elements.forEach((element) => {
    element.textContent = 'Tested';
});

Jeśli chcesz mieć najbardziej wydajny wariant dla wszystkich przeglądarek, może to być ten:

var elements = document.getElementsByClassName('testClass');
Array.from(elements).map(
    (element) => {
        return element.textContent = 'Tested';
    }
);
StefanSL
źródło