Dlaczego nodelist nie ma forEach?

91

Pracowałem nad krótkim skryptem do zmiany <abbr>wewnętrznego tekstu elementów, ale okazało się, że nodelistnie ma on forEachmetody. Wiem, że nodelistto nie dziedziczy po Array, ale czy nie wydaje się, że forEachbyłoby to przydatne? Czy jest jakiś szczególny problem realizacja nie jestem świadomy, który uniemożliwia dodanie forEachdo nodelist?

Uwaga: zdaję sobie sprawę, że Dojo i jQuery mają forEachw jakiejś formie swoje listy węzłów. Nie mogę też używać ze względu na ograniczenia.

Węże i kawa
źródło
6
Witam z przyszłości! nodeList ma forEach od ES6 .
Blaise,

Odpowiedzi:

94

NodeList ma teraz forEach () we wszystkich głównych przeglądarkach

Zobacz nodeList forEach () w MDN .

Oryginalna odpowiedź

Żadna z tych odpowiedzi nie wyjaśnia, dlaczego NodeList nie dziedziczy po tablicy, co pozwala jej mieć forEachi całą resztę.

Odpowiedź znajduje się w tym wątku es-dyskusji . Krótko mówiąc, łamie sieć:

Problemem był kod, który niepoprawnie zakładał, że instanceof oznacza, że ​​instancja była Array w połączeniu z Array.prototype.concat.

Wystąpił błąd w bibliotece zamknięć Google, który spowodował, że prawie wszystkie aplikacje Google przestały działać z tego powodu. Biblioteka została zaktualizowana, gdy tylko została znaleziona, ale nadal może istnieć kod, który w połączeniu z concat przyjmuje takie same nieprawidłowe założenie.

Oznacza to, że jakiś kod zrobił coś podobnego

if (x instanceof Array) {
  otherArray.concat(x);
} else {
  doSomethingElseWith(x);
}

Jednak concat„prawdziwe” tablice (nie instancje Array) będą traktować inaczej niż inne obiekty:

[1, 2, 3].concat([4, 5, 6]) // [1, 2, 3, 4, 5, 6]
[1, 2, 3].concat(4) // [1, 2, 3, 4]

oznacza to, że powyższy kod zepsuł się, gdy xbył NodeList, ponieważ wcześniej poszedł w dół doSomethingElseWith(x)ścieżki, podczas gdy później poszedł w dół otherArray.concat(x)ścieżki, co zrobiło coś dziwnego, ponieważ xnie był prawdziwą tablicą.

Przez pewien czas istniała propozycja Elementsklasy, która byłaby prawdziwą podklasą Array i miałaby służyć jako „nowa lista NodeList”. Jednak zostało to usunięte ze standardu DOM , przynajmniej na razie, ponieważ nie było to jeszcze możliwe do zaimplementowania z różnych powodów technicznych i specyfikacji.

Domenic
źródło
11
Wydaje mi się, że to zły telefon. Poważnie, myślę, że to właściwa decyzja, aby od czasu do czasu zepsuć różne rzeczy, szczególnie jeśli oznacza to, że mamy rozsądne interfejsy API na przyszłość. Poza tym, to nie jest tak, że internet jest nawet bliski bycia stabilną platformą, ludzie są przyzwyczajeni do tego, że ich 2-letni javascript nie działa już zgodnie z oczekiwaniami ..
JoyalToTheWorld
3
„Breaks the web”! = „Breaks the Google stuff”
Matt
Dlaczego nie pożyczyć metody forEach z Array.prototype? Np. Zamiast dodawać Array jako prototyp, po prostu zrób this.forEach = Array.prototype.forEach w konstruktorze, czy nawet po prostu zaimplementuj forEach w sposób unikalny dla NodeList?
PopKernel
1
Uwaga; ta aktualizacja NodeList now has forEach() in all major browserswydaje się sugerować, że IE nie jest główną przeglądarką. Mam nadzieję, że to prawda w przypadku niektórych osób, ale nie jest to prawda dla mnie (jeszcze).
Graham P Heath
58

Możesz to zrobić

Array.prototype.forEach.call (nodeList, function (node) {

    // Your code here.

} );
akuhn
źródło
3
Array.prototype.forEach.callmożna [].forEach.call
skrócić
7
@CodeBrauer: to nie tylko skracanie Array.prototype.forEach.call, to tworzenie pustej tablicy i używanie jej forEachmetody.
Paul D. Waite
34

Możesz rozważyć utworzenie nowej tablicy węzłów.

  var nodeList = document.getElementsByTagName('div'),

      nodes = Array.prototype.slice.call(nodeList,0); 

  // nodes is an array now.
  nodes.forEach(function(node){ 

       // do your stuff here.  

  });

Uwaga: to jest tylko lista / tablica odniesień do węzłów, które tutaj tworzymy, bez zduplikowanych węzłów.

  nodes[0] === nodeList[0] // will be true
sbr
źródło
22
Albo po prostu zrób Array.prototype.forEach.call(nodeList, fun).
akuhn
Chciałbym również zasugerować aliasing funkcji foreach tak, że: var forEach = Array.prototype.forEach.call(nodeList, callback);. Teraz możesz po prostu zadzwonićforEach(nodeList, callback);
Andrew Craswell,
19

Nigdy nie mów nigdy, jest rok 2016, a NodeListobiekt ma zaimplementowaną forEachmetodę w najnowszym chrome (v52.0.2743.116).

Jest za wcześnie, aby używać go w produkcji, ponieważ inne przeglądarki jeszcze tego nie obsługują (testowane FF 49), ale myślę, że wkrótce zostanie to ustandaryzowane.

maioman
źródło
2
Opera również ją obsługuje i zostanie dodana obsługa przeglądarki Firefox w wersji 50, której premiera zaplanowana jest na 15/11/16.
Shaggy
1
Chociaż jest wdrożony, nie jest częścią żadnego standardu. Nadal najlepiej jest zrobić Array.prototype.slice.call(nodelist).forEach(…)to, co jest standardowe i działa w starych przeglądarkach.
Nate
17

Krótko mówiąc, implementacja tej metody jest konfliktem projektowym.

Z MDN:

Dlaczego nie mogę używać forEach ani mapować na NodeList?

NodeList są używane bardzo podobnie jak tablice i kuszące byłoby użycie na nich metod Array.prototype. Jest to jednak niemożliwe.

JavaScript posiada mechanizm dziedziczenia oparty na prototypach. Instancje tablic dziedziczą metody tablicowe (takie jak forEach lub map), ponieważ ich łańcuch prototypów wygląda następująco:

myArray --> Array.prototype --> Object.prototype --> null (łańcuch prototypów obiektu można uzyskać, wywołując kilka razy Object.getPrototypeOf)

forEach, map i like są własnymi właściwościami obiektu Array.prototype.

W przeciwieństwie do tablic łańcuch prototypów NodeList wygląda następująco:

myNodeList --> NodeList.prototype --> Object.prototype --> null

NodeList.prototype zawiera metodę item, ale żadnej z metod Array.prototype, więc nie można ich używać na NodeLists.

Źródło: https://developer.mozilla.org/en-US/docs/DOM/NodeList (przewiń w dół do Dlaczego nie mogę używać forEach lub mapować na NodeList? )

Matt Lo
źródło
8
Skoro jest to lista, dlaczego została zaprojektowana w ten sposób? Co powstrzymało ich przed zrobieniem łańcucha myNodeList --> NodeList.prototype --> Array.prototype --> Object.prototype --> null:?
Igor Pantović
14

Jeśli chcesz używać forEach na NodeList, po prostu skopiuj tę funkcję z Array:

NodeList.prototype.forEach = Array.prototype.forEach;

To wszystko, teraz możesz go używać w taki sam sposób, jak w przypadku tablicy:

document.querySelectorAll('td').forEach(function(o){
   o.innerHTML = 'text';
});
AlexTR
źródło
4
Tyle że mutowanie klas bazowych nie jest zbyt wyraźne dla przeciętnego czytelnika. Innymi słowy, w części kodu musisz pamiętać o każdym dostosowaniu obiektów podstawowych przeglądarki. Poleganie na dokumentacji MDN nie jest już przydatne, ponieważ obiekty zmieniły zachowanie w stosunku do normy. Lepiej jest jawnie zastosować prototyp podczas rozmowy telefonicznej, aby czytelnik mógł łatwo zrozumieć, że forEach jest zapożyczonym pomysłem, a nie czymś, co jest częścią definicji języka. Zobacz odpowiedź, którą @akuhn ma powyżej.
Sukima
@Sukima została wprowadzona w błąd przez niewłaściwe przesłanki. W tym konkretnym przypadku podane podejście naprawia NodeList, aby zachowywał się zgodnie z oczekiwaniami programisty. Jest to najwłaściwszy sposób rozwiązania problemu z klasą systemu. (NodeList wydaje się być niedokończony i powinien zostać poprawiony w przyszłych wersjach językowych.)
Daniel Garmoshka
1
teraz, gdy istnieje NodeList.forEach, staje się to zabawnie prostym wypełnieniem!
Damon
7

W ES2015 możesz teraz używać forEachmetody do nodeList.

document.querySelectorAll('abbr').forEach( el => console.log(el));

Zobacz łącze MDN

Jeśli jednak chcesz użyć kolekcji HTML lub innych obiektów podobnych do tablic, w es2015 możesz użyć Array.from()metody method. Ta metoda przyjmuje obiekt przypominający tablicę lub obiekt iterowalny (w tym nodeList, kolekcje HTML, ciągi znaków itp.) I zwraca nową instancję Array. Możesz go używać w ten sposób:

const elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( el => console.log(el));

Ponieważ Array.from()metoda jest shimmable, możesz jej użyć w kodzie es5 w ten sposób

var elements = document.getElementsByTagName('abbr');
Array.from(elements).forEach( function(el) {
    console.log(el);
});

Szczegółowe informacje można znaleźć na stronie MDN .

Aby sprawdzić aktualną obsługę przeglądarek .

LUB

Innym sposobem na es2015 jest użycie operatora spreadu.

[...document.querySelectorAll('abbr')].forEach( el => console.log(el));

Operator rozprzestrzeniania MDN

Operator rozprzestrzeniania - obsługa przeglądarki

Mishel Tanvir Habib
źródło
2

Moje rozwiązanie:

//foreach for nodeList
NodeList.prototype.forEach = Array.prototype.forEach;
//foreach for HTML collection(getElementsByClassName etc.)
HTMLCollection.prototype.forEach = Array.prototype.forEach;
Bakos Bence
źródło
1
Często nie jest dobrym pomysłem rozszerzenie funkcjonalności DOM poprzez prototypy, szczególnie w starszych wersjach IE ( artykuł ).
KFE
0

NodeList jest częścią DOM API. Spójrz na powiązania ECMAScript, które mają również zastosowanie do JavaScript. http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html . NodeList i właściwość długości tylko do odczytu i funkcja elementu (indeksu) do zwracania węzła.

Odpowiedź brzmi: musisz iterować. Nie ma alternatywy. Foreach nie zadziała. Pracuję z powiązaniami API Java DOM i mam ten sam problem.

randominstanceOfLivingThing
źródło
Ale czy jest jakiś szczególny powód, dla którego nie powinno się go wdrażać? Zarówno jQuery, jak i Dojo zaimplementowały to we własnych bibliotekach
Snakes and Coffee
2
ale jak to jest konflikt projektowy?
Węże i kawa
0

Sprawdź w MDN specyfikację NodeList.forEach .

NodeList.forEach(function(item, index, nodeList) {
    // code block here
});

W IE użyj odpowiedzi akuhna :

[].forEach.call(NodeList, function(item, index, array) {
    // code block here
});
VesperX
źródło