Reputation: 28144
What is the most efficient way to filter or map a nodelist in ES6?
Based on my readings, I would use one of the following options:
[...nodelist].filter
or
Array.from(nodelist).filter
Which one would you recommend? And are there better ways, for example without involving arrays?
Upvotes: 192
Views: 140596
Reputation: 441
Filter or map nodelists in ES6 I came out of this simple function. See https://developer.mozilla.org/en-US/docs/Web/API/NodeList/entries#example
function filterNodeList(NodeList, callback) {
if (typeof callback !== "function") callback = (i) => i; // Any better idea?
const Result = document.createElement("div");
//# No need to filter empty NodeList
if (NodeList.length === 0) return NodeList;
for (let i = 0; i < NodeList.length; i++) {
if (callback(NodeList.item(i))){
Result.appendChild(NodeList.item(i));
i--; //If you don't want to clone
}
}
return Result.childNodes;
}
Upvotes: 1
Reputation: 2088
Using ECMAS 2016:
let nodes = [...document.querySelector('__SELECTOR__').childNodes].filter(item => item.nodeType === 1);
Upvotes: -1
Reputation: 15032
[...a].filter
vs Array.from(a).filter
Not a "real" difference in performance, Array.from
could be a very very tiny bit faster because you're not creating a new Array
on the "JS level", but it happens directly in the native code.
However for performance (and to also avoid "Array
-ing") you should consider why are you filtering a NodeList
and where/how did you get it. In many cases, you just want a particular element either by id
or by class
or other CSS selector.
document.querySelectorAll
is like 10x - 200x faster and works for any CSS selector
document.getElementById
is even faster (but of course requires an id
)
You can even optimize querySelectorAll
or bypass "not-yet-known" case if you provide a pre-stored parent to look in, let me give you an example:
let mainbar = document.getElementById('mainbar');
mainbar.querySelectorAll('.flex--item');
is almost 10x faster than
Array.from(a).filter(el => el.classList.contains("flex--item"))
Also be aware that document.querySelectorAll('#mainbar .flex--item');
is still about 5x faster than Array
filtering, but about 2x slower than pre-storing the parent with an id
.
NodeList
(it may be empty, but it still will be NodeList
) and that goes for both document.querySelectorAll()
and Element.querySelectorAll()
Upvotes: 8
Reputation: 17120
TL;DR;
Array.prototype.slice.call(nodelist).filter
The slice() method returns an array.
That returned array is a shallow copy of collection (NodeList)
So it works faster than Array.from()
So it works as fast as Array.from()
Elements of the original collection are copied into the returned array as follows:
Short explanation regarding arguments
Array.prototype.slice(beginIndex, endIndex)
Array.prototype.slice.call(namespace, beginIndex, endIndex)
Upvotes: 20
Reputation: 4242
How about this:
// Be evil. Extend the prototype.
if (window.NodeList && !NodeList.prototype.filter) {
NodeList.prototype.filter = Array.prototype.filter;
}
// Use it like you'd expect:
const noClasses = document
.querySelectorAll('div')
.filter(div => div.classList.length === 0)
It's the same approach as mentioned in the MDN docs for NodeList.forEach (under 'Polyfill'), it works for IE11, Edge, Chrome and FF.
Upvotes: 7
Reputation: 1354
I found a reference that uses map
directly on the NodeList by
Array.prototype.map.call(nodelist, fn)
I haven't tested it, but it seems plausible that this would be faster because it should access the NodeList directly.
Upvotes: 25
Reputation: 161477
[...nodelist]
will make an array of out of an object if the object is iterable.Array.from(nodelist)
will make an array out of an object if the object is iterable or if the object is array-like (has .length
and numeric props)Your two examples will be identical if NodeList.prototype[Symbol.iterator]
exists, because both cases cover iterables. If your environment has not been configured such that NodeList
is iterable however, your first example will fail, and the second will succeed. Babel
currently does not handle this case properly.
So if your NodeList
is iterable, it is really up to you which you use. I would likely choose on a case-by-case basis. One benefit of Array.from
is that it takes a second argument of a mapping function, whereas the first [...iterable].map(item => item)
would have to create a temporary array, Array.from(iterable, item => item)
would not. If you are not mapping the list however, it does not matter.
Upvotes: 266