forEach on querySelectorAll not working in recent Microsoft browsers
Most DOM methods and collection properties aren't actually arrays, they're collections:
querySelectorAll
returns a staticNodeList
(a snapshot of matching elements as of when you call it).getElementsByTagName
,getElementsByTagNameNS
,getElementsByClassName
, and thechildren
property on aParentNode
(Elements are parent nodes) return liveHTMLCollection
instances (if you change the DOM, that change is reflected live in the collection).getElementsByName
returns a liveNodeList
(not a snapshot).
NodeList
only recently got forEach
(and keys
and a couple of other array methods). HTMLCollection
didn't and won't; it turned out adding them would break too much code on the web.
Both NodeList
and HTMLCollection
are iterable, though, meaning that you can loop through them with for-of
, expand them into an array via spread ([...theCollection]
), etc. But if you're running on a browser where NodeList
doesn't have forEach
, it's probably too old to have any ES2015+ features like for-of
or iteration.
Since NodeList
is specified to have forEach
, you can safely polyfill it, and it's really easy to do:
if (typeof NodeList !== "undefined" && NodeList.prototype && !NodeList.prototype.forEach) {
// Yes, there's really no need for `Object.defineProperty` here
NodeList.prototype.forEach = Array.prototype.forEach;
}
Direct assignment is fine in this case, because enumerable
, configurable
, and writable
should all be true
and it's a value property. (enumerable
being true
surprised me, but that's how it's defined natively on Chrome/Chromium/Edge/Etc., Firefox, the old Legacy Edge, and Safari).
In your own code, you can do that with HTMLCollection
as well if you want, just beware that if you're using some old DOM libs like MooTools or YUI or some such, they may be confused if you add forEach
to HTMLCollection
.
As I said before, NodeList
and HTMLCollection
are both specified to be iterable (because of this Web IDL rule¹). If you run into a browser that has ES2015+ features but doesn't make the collections iterable for some reason, you can polyfill that, too:
if (typeof Symbol !== "undefined" && Symbol.iterator && typeof NodeList !== "undefined" && NodeList.prototype && !NodeList.prototype[Symbol.iterator]) {
Object.defineProperty(NodeList.prototype, Symbol.iterator, {
value: Array.prototype[Symbol.iterator],
writable: true,
configurable: true
});
}
(And the same for HTMLCollection
.)
Here's a live example using both, try this on (for instance) IE11 (although it will only demonstrate forEach
), on which NodeList
doesn't have these features natively:
// Using only ES5 features so this runs on IE11
function log() {
if (typeof console !== "undefined" && console.log) {
console.log.apply(console, arguments);
}
}
if (typeof NodeList !== "undefined" && NodeList.prototype) {
// forEach
if (!NodeList.prototype.forEach) {
// Yes, there's really no need for `Object.defineProperty` here
console.log("Added forEach");
NodeList.prototype.forEach = Array.prototype.forEach;
}
// Iterability - won't happen on IE11 because it doesn't have Symbol
if (typeof Symbol !== "undefined" && Symbol.iterator && !NodeList.prototype[Symbol.iterator]) {
console.log("Added Symbol.iterator");
Object.defineProperty(NodeList.prototype, Symbol.iterator, {
value: Array.prototype[Symbol.iterator],
writable: true,
configurable: true
});
}
}
log("Testing forEach");
document.querySelectorAll(".container div").forEach(function(div) {
var html = div.innerHTML;
div.innerHTML = html[0].toUpperCase() + html.substring(1).toLowerCase();
});
// Iterable
if (typeof Symbol !== "undefined" && Symbol.iterator) {
// Using eval here to avoid causing syntax errors on IE11
log("Testing iterability");
eval(
'for (const div of document.querySelectorAll(".container div")) { ' +
' div.style.color = "blue"; ' +
'}'
);
}
<div class="container">
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
</div>
phantomjs: document.querySelectorAll() not working for dynamic page
Thanks for the answers Leftium and James, I've tried waitFor.js and other suggestions on Stack Overflow. But none of them worked. Now I am using Nightmare.js and it's working now, using Nighmare.js - Asynchronous operations and loops and Looping through pages when next is available #402
But knowing how to do it with phantom.js will be nice, though
getElementsByClassName().forEach() function in JavaScript not working
The browser is showing an error: document.getElementsByClassName(...).forEach is not a function
That's because getElementsByClassName
doesn't return an array, it returns an HTMLCollection
. They don't have a forEach
method (yet; it may at some point, or not).
You can use the one that arrays have like this:
Array.prototype.forEach.call(document.getElementsByClassName("oneResult"), function(element) {
// Use `element` here
});
Or on modern browsers (or with a polyfill) you can create an array from the collection:
Array.from(document.getElementsByClassName("oneResult")).forEach(function(element) {
// Use `element` here
});
Another option is to add forEach
to HTMLCollection
, which you can do like this on any vaguely-modern browser (even IE8, if you polyfill Array.prototype.forEach
first):
if (typeof HTMLCollection !== "undefined" && HTMLCollection.prototype && !HTMLCollection.prototype.forEach) {
Object.defineProperty(HTMLCollection.prototype, "forEach", {
value: Array.prototype.forEach,
configurable: true,
writable: true
});
}
Finally, note that while HTMLCollection
doesn't have forEach
, the NodeList
returned by querySelectorAll
does, though on some older browsers it may need to be polyfilled. See this answer about polyfilling NodeList
if necessary.
More:
- For-each over an array in JavaScript? (some answers there -- including mine -- have a section on "array-like" things, which includes
HTMLCollection
s) - What do querySelectorAll, getElementsByClassName and other getElementsBy* methods return?
Why does Javascript collapsible not work in IE?
IE doesn't support arrow function and forEach
for HTMLCollection.
You could add the following polyfill in your script to polyfill forEach
then it can be used for both NodeList and HTMLCollection in IE:
//polyfill
var ctors = [typeof NodeList !== "undefined" && NodeList, typeof HTMLCollection !== "undefined" && HTMLCollection];
for (var n = 0; n < ctors.length; ++n) {
var ctor = ctors[n];
if (ctor && ctor.prototype && !ctor.prototype.forEach) {
// (Yes, there's really no need for `Object.defineProperty` when doing the `forEach`)
ctor.prototype.forEach = Array.prototype.forEach;
if (typeof Symbol !== "undefined" && Symbol.iterator && !ctor.prototype[Symbol.iterator]) {
Object.defineProperty(ctor.prototype, Symbol.iterator, {
value: Array.prototype[Symbol.itereator],
writable: true,
configurable: true
});
}
}
}
Besides, you should transpile the arrow functions in your code by editing the two functions in your script like below:
function showexplanation() {
document.querySelectorAll('.accordion-content').forEach(function (item) {
item.style.display = "table-row";
});
document.querySelectorAll('.arrow-right').forEach(function (item) {
item.style.transform = "rotate(45deg)";
});
}
function hideAll() {
document.querySelectorAll('.accordion-content').forEach(function (item) {
item.style.display = "none";
});
document.querySelectorAll('.arrow-right').forEach(function (item) {
item.style.transform = "rotate(-45deg)";
});
}
.forEach() on Arrays vs NodeList (JavaScript)
Look
NodeLists and Arrays are two different things because NodeLists are
actually not a JavaScript API, but a browser API.
Things like querySelectorAll()
and getElementsByTagName()
aren’t JavaScript methods, they’re browser APIs that let you access DOM elements. You can then manipulate them with JavaScript.
NodeLists differ from Arrays in another meaningful way, too.
They are often live lists, meaning that if elements are removed or added to the DOM, the list updates automatically. querySelector()
and querySelectorAll()
return a static list (one that doesn’t update), but properties like .childNodes are live lists that will change as you manipulate the DOM (which can be a good or bad thing, depending on how you’re using it).
This is all made more confusing because arrays can contain nodes. And, there’s another, older type of list called an HTMLCollection that predates NodeLists, but is functionally similar (another article for another day).
The key way to think about NodeLists vs. Arrays: NodeLists are a
language-agnostic way to access DOM elements, and Arrays are a
JavaScript object you can use to contain collections of stuff.
They each have their own methods and properties, and you can convert a NodeList into an Array if you need to (but not the other way around).
This javascript code doesn't work in Internet Explorer 11
Neither the arrow functions nor the forEach() method are compatible in IE11. You need to rewrite the above using ES5 like this:
<script>
const images = document.querySelectorAll('.view-content img');
for(var i = 0; i < images.length; i++) {
let src = images[i].getAttribute('data-original');
let newSrc = 'https://example.com' + src;
images[i].setAttribute('src', newSrc);
}
</script>
Or if you prefer using ES6 to write your JavaScript, you can use a toolchain like BabelJS to compile your JavaScript ES6 to JavaScript ES5 on production.
Why do we create a variable to reference a class instead of just using document.querySelector(.class)?
Why do we need to define the var first and then run the loop?
You don't.
You might choose to, if you're going to do more than one thing with that set of elements (because unlike getElementsByTagName
, querySelectorAll
doesn't reuse the collection; every call to it re-queries the DOM). But if you're doing just one thing, there's no reason why you can't do it as shown in your second example.
Side note: The NodeList
from querySelectorAll
only has forEach
on relatively-modern browsers. But you can polyfill it trivially for older ones; this answer shows how.
Related Topics
How to Get the HTML for a Dom Element in JavaScript
How to "Reset" <Div> to Its Original State After It Has Been Modified by JavaScript
Html5 Canvas Ctx.Filltext Won't Do Line Breaks
Programmatically Play Video with Sound on Safari and Mobile Chrome
Parse Date Without Timezone JavaScript
Vue.Js Get Selected Option on @Change
Jquery Select Option Click Handler
How Does _Proto_ Differ from Constructor.Prototype
Get Value of Input Field Inside an Iframe
Anyone Have a Diff Algorithm for Rendered HTML
Jquery - How to Select by Attribute
Putting HTML Inside an Iframe (Using JavaScript)