Why doesn't nodelist have forEach?

JavascriptArraysDomForeach

Javascript Problem Overview


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.

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

You can do

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

    // Your code here.

} );

Solution 3 - Javascript

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

Solution 4 - Javascript

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.

Solution 5 - Javascript

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?)

Solution 6 - Javascript

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

Solution 7 - Javascript

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

Solution 8 - Javascript

My solution:

//foreach for nodeList
NodeList.prototype.forEach = Array.prototype.forEach;
//foreach for HTML collection(getElementsByClassName etc.)
HTMLCollection.prototype.forEach = Array.prototype.forEach;

Solution 9 - Javascript

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.

Solution 10 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSnakes and CoffeeView Question on Stackoverflow
Solution 1 - JavascriptDomenicView Answer on Stackoverflow
Solution 2 - JavascriptakuhnView Answer on Stackoverflow
Solution 3 - JavascriptsbrView Answer on Stackoverflow
Solution 4 - JavascriptmaiomanView Answer on Stackoverflow
Solution 5 - JavascriptMatt LoView Answer on Stackoverflow
Solution 6 - JavascriptAlexTRView Answer on Stackoverflow
Solution 7 - JavascriptMishel Tanvir HabibView Answer on Stackoverflow
Solution 8 - JavascriptBakos BenceView Answer on Stackoverflow
Solution 9 - JavascriptrandominstanceOfLivingThingView Answer on Stackoverflow
Solution 10 - JavascriptVesperXView Answer on Stackoverflow