18

Something nice with plain javascript would be to be able to use forEach, map, filter, etc, on the items returned by document.querySelectorAll, document.getElementsBy* etc.

This'd lead to less dependency on jQuery, and simply cleaner code. Right now, this is how we can do it, in an ugly way:

[].forEach.call( document.querySelectorAll(sel), function(el) {
});

This is... verbose.

Any way to be able to use forEach and the likes right away on the elements returned?

Florian Margaine
  • 58,730
  • 15
  • 91
  • 116

4 Answers4

14

A naive way would be to do this if you tested on Chrome:

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

This works. On Webkit. It doesn't on Firefox though. Because FF returns an HTMLCollection...

The most cross-browser way I've found:

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

It doesn't work on IE8 and lower though, because they choke when adding properties to host objects prototypes.

Full list:

NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;
NodeList.prototype.map = HTMLCollection.prototype.map = Array.prototype.map;
NodeList.prototype.filter = HTMLCollection.prototype.filter = Array.prototype.filter;
NodeList.prototype.reduce = HTMLCollection.prototype.reduce = Array.prototype.reduce;
NodeList.prototype.reduceRight = HTMLCollection.prototype.reduceRight = Array.prototype.reduceRight;
NodeList.prototype.every = HTMLCollection.prototype.every = Array.prototype.every;
NodeList.prototype.some = HTMLCollection.prototype.some = Array.prototype.some;

Or, to please our dear Bergi (and also because it is cleaner):

['forEach', 'map', 'filter', 'reduce', 'reduceRight', 'every', 'some'].forEach(
    function(p) {
    NodeList.prototype[p] = HTMLCollection.prototype[p] = Array.prototype[p];
});

Considering the link to perfectionkills, it's mostly irrelevant there. The problem is that the DOM is mostly not behaving the same on browsers when extended. This modification is sane in all the browsers except IE <=8.

Florian Margaine
  • 58,730
  • 15
  • 91
  • 116
6
function forEach( a, fn ) {
    return [].forEach.call(a, fn);
};

forEach(document.querySelectorAll(sel), function(el) {
});

And many more:

function map( a, fn ) {
    return [].map.call(a, fn);
};
function filter( a, fn ) {
    return [].filter.call(a, fn);
};
function reduce( a, fn ) {
    return [].reduce.call(a, fn);
};
function reduceRight( a, fn ) {
    return [].reduceRight.call(a, fn);
};
function every( a, fn ) {
    return [].every.call(a, fn);
};
function some( a, fn ) {
    return [].some.call(a, fn);
};

Maybe you will need

[].slice.call(a)

in some situations.

function forEach(a, fn) {
    return [].forEach.call([].slice.call(a), fn);
}
Andreas Louv
  • 46,145
  • 13
  • 104
  • 123
  • Uh, yeah, forgot to mention that. I'd rather use the `bind` method though (I don't remember off my head how to do it). But I still prefer the way shown in my answer out of these. It feels more "dommy". – Florian Margaine Dec 20 '12 at 10:24
  • @FlorianMargaine I think someone is rep whoring?? – Andreas Louv Dec 20 '12 at 10:26
  • 1
    Nope. To be honest, the question/answer was for hat hunting. I honestly prefer the way shown in my answer. As said in my previous comment, I did know about your way already -- I just don't especially like it. Besides, why rep whoring when I'm already rep capped?! – Florian Margaine Dec 20 '12 at 13:29
2

If you don't like changing prototypes and want all array functions to just work, it may be easier to just convert your collection to an array:

Array.from(document.querySelectorAll('a'))

All array functions will be available, no need to update your code when new version of JavaScript is released:

Array.from(document.querySelectorAll('a')).forEach(a => console.log(a))
Athari
  • 33,702
  • 16
  • 105
  • 146
0

forEach can now be used directly without conversions

https://developer.mozilla.org/en-US/docs/Web/API/NodeList

Walle Cyril
  • 3,087
  • 4
  • 23
  • 55