Why doesn't nodelist have forEach?
Asked Answered
S

11

109

I was working on a short script to change <abbr> elements' inner text, but found that nodelist does not have a forEach method. I know that nodelist doesn't inherit from Array, but doesn't it seem like forEach would be a useful method to have? Is there a particular implementation issue I am not aware of that prevents adding forEach to nodelist?

Note: I am aware that Dojo and jQuery both have forEach in some form for their nodelists. I cannot use either due to limitations.

Sangsanger answered 17/11, 2012 at 19:5 Comment(3)
Hello from the future! nodeList has forEach since ES6.Nuthouse
@Nuthouse Not really since ES6. Array.prototype.forEach exists since ECMAScript 5, and indeed, NodeList.prototype.forEach === Array.prototype.forEach. But this fact is not specified in ECMAScript, but in WebIDL.Mouthful
To use .forEach() when selecting by class, getElementsByClassName() returns an HTMLCollection without .forEach(), use querySelectorAll('.classname') that returns a nodeList.Josefjosefa
F
111

NodeList now has forEach() in all major browsers

See nodeList forEach() on MDN.

Original answer

None of these answers explain why NodeList doesn't inherit from Array, thus allowing it to have forEach and all the rest.

The answer is found on this es-discuss thread. In short, it breaks the web:

The problem was code that incorrectly assumed instanceof to mean that the instance was an Array in combination with Array.prototype.concat.

There was a bug in Google's Closure Library which caused almost all Google's apps to fail due to this. The library was updated as soon as this was found but there might still be code out there that makes the same incorrect assumption in combination with concat.

That is, some code did something like

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

However, concat will treat "real" arrays (not instanceof Array) differently from other objects:

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

so that means that the above code broke when x was a NodeList, because before it went down the doSomethingElseWith(x) path, whereas afterward it went down the otherArray.concat(x) path, which did something weird since x wasn't a real array.

For some time there was a proposal for an Elements class that was a real subclass of Array, and would be used as "the new NodeList". However, that was removed from the DOM Standard, at least for now, since it wasn't feasible to implement yet for a variety of technical and specification-related reasons.

Fulk answered 19/11, 2014 at 18:25 Comment(6)
Seems like a bad call to me. Seriously, I think its the right decision to break stuff once in a while, particularly if it means we have sane APIs for the future. Besides, its not like the web is even close to being a stable platform, people are used to their 2 year old javascript no longer functioning as expected..Cartel
"Breaks the web" != "Breaks Google stuff"Abode
Why not just borrow the forEach method from Array.prototype? E.g. instead of adding Array as the prototype, just do this.forEach = Array.prototype.forEach in the constructor, or even just implement forEach uniquely for NodeList?Joelynn
Note; this update NodeList now has forEach() in all major browsers seems to imply that IE is not a major browser. Hopefully that's true for some people, but it's not true for me (yet).Iata
Odd that as of November 2021, it's still not included in the whatwg spec.Simla
It is implicit in the iterable<Node> declarationFulk
K
67

You can do

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

    // Your code here.

} );
Keister answered 14/3, 2014 at 22:7 Comment(3)
Array.prototype.forEach.call can be shortend to [].forEach.callIson
@CodeBrauer: that’s not just shortening Array.prototype.forEach.call, it’s creating an empty array and using its forEach method.Salimeter
As far as I know Array.prototype.forEach.call is the fastest method while not allocating additional memory.Deth
T
33

You can consider creating a new array of nodes.

  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.  

  });

Note: This is just a list/array of node references we are creating here, no duplicate nodes.

  nodes[0] === nodeList[0] // will be true
Tommie answered 13/3, 2013 at 22:58 Comment(2)
Or just do Array.prototype.forEach.call(nodeList, fun).Keister
I would also suggest aliasing the forEach function such that: var forEach = Array.prototype.forEach.call(nodeList, callback);. Now you can simply call forEach(nodeList, callback);Bruin
B
20

In short, its a design conflict to implement that method.

From MDN:

Why can't I use forEach or map on a NodeList?

NodeList are used very much like arrays and it would be tempting to use Array.prototype methods on them. This is, however, impossible.

JavaScript has an inheritance mechanism based on prototypes. Array instances inherit array methods (such as forEach or map) because their prototype chain looks like the following:

myArray --> Array.prototype --> Object.prototype --> null (the prototype chain of an object can be obtained by calling several times Object.getPrototypeOf)

forEach, map and the likes are own properties of the Array.prototype object.

Unlike arrays, NodeList prototype chain looks like the following:

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

NodeList.prototype contains the item method, but none of the Array.prototype methods, so they cannot be used on NodeLists.

Source: https://developer.mozilla.org/en-US/docs/DOM/NodeList (scroll down to Why can't I use forEach or map on a NodeList?)

Balikpapan answered 17/11, 2012 at 19:10 Comment(1)
So, since it's a list, why is it designed that way? What was stopping them to make chain: myNodeList --> NodeList.prototype --> Array.prototype --> Object.prototype --> null ?Format
B
20

Never say never, it's 2016 and the NodeList object has implemented a forEach method in latest chrome (v52.0.2743.116).

It's too early to use it in production as other browser don't support this yet (tested FF 49) but I would guess that this will be standardized soon.

Bauhaus answered 24/8, 2016 at 21:25 Comment(2)
Opera also supports it and support will be added in v50 of Firefox, scheduled for release on 15/11/16.Bronnie
Although implemented, it’s not part of any standard. It’s still best to do Array.prototype.slice.call(nodelist).forEach(…) which is standard and works in old browsers.Vibrator
C
16

If you would like using forEach on NodeList, just copy that function from Array:

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

Thats all, now you can use it at the same manner you would for Array:

document.querySelectorAll('td').forEach(function(o){
   o.innerHTML = 'text';
});
Coaxial answered 31/5, 2014 at 18:30 Comment(3)
Except that mutating the base classes is not very explicit to the average reader. In other words deep in some code you have to remember every customization you make to the browser base objects. Relying on the MDN documentation is no longer useful because the objects have changed behavior from the norm. It is better to explicitly apply the prototype at call time so the reader can easily realize that forEach is a borrowed idea and not something that is part of the language definition. See the answer @Keister has above.Globate
@Globate misguided by wrong premises. In this specific case, given approach fixes NodeList to behave as expected by developer. This is the most proper way of fixing system Class issue. (NodeList seems to be unfinished and should be fixed in future versions of language.)Heelpost
now that NodeList.forEach exists, this becomes a hilariously simple polyfill!Groping
D
13

In ES2015, you can now use forEach method to the nodeList.

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

See The MDN Link

However if you want to use HTML Collections or other array-like objects, in es2015, you can use Array.from() method. This method takes an array-like or iterable object (including nodeList, HTML Collections, strings etc) and returns a new Array instance. You can use it like this:

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

As Array.from() method is shimmable, you can use it in es5 code like this

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

For details, see the MDN page.

To check current browser support.

OR

another es2015 way is to use spread operator.

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

MDN spread operator

Spread Operator - Browser Support

Deponent answered 15/6, 2018 at 5:31 Comment(0)
L
1

My solution:

//foreach for nodeList
NodeList.prototype.forEach = Array.prototype.forEach;
//foreach for HTML collection(getElementsByClassName etc.)
HTMLCollection.prototype.forEach = Array.prototype.forEach;
Ledge answered 12/11, 2017 at 19:14 Comment(1)
It's often not a good idea to extend the functionality of DOM through prototypes, especially in older versions of IE (article).Nachison
R
1

You can add forEach polyfill for old browsers just one line of code:

window.NodeList && !NodeList.prototype.forEach && (NodeList.prototype.forEach = Array.prototype.forEach);

https://udn.realityripple.com/docs/Web/API/NodeList/forEach

Rawdon answered 19/2, 2023 at 7:29 Comment(0)
S
0

NodeList is part of the DOM API. Look at the ECMAScript bindings which apply to JavaScript as well. http://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html. The nodeList and a read-only length property and item(index) function to return a node.

The answer is, you have to iterate. There is no alternative. Foreach will not work. I work with Java DOM API bindings and have the same problem.

Saith answered 17/11, 2012 at 19:14 Comment(2)
But is there a particular reason why it shouldn't be implemented? Both jQuery and Dojo have implemented it in their own librariesSangsanger
but how is it a design conflict?Sangsanger
G
0

Check MDN for NodeList.forEach specification.

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

In IE, use akuhn's answer:

[].forEach.call(NodeList, function(item, index, array) {
    // code block here
});
Gorgoneion answered 1/10, 2018 at 23:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.