When is NodeList live and when is it static?
Information about each method details if it is live or not, but there does not seem to be a standard convention for determining it.
document.getElementsByClassName()
is an HTMLCollection
, and is live.
document.getElementsByTagName()
is an HTMLCollection
, and is live.
document.getElementsByName()
is a NodeList
and is live.
document.querySelectorAll()
is a NodeList
and is not live.
HTMLCollection
s appear to always be live
An HTMLCollection is a list of nodes. An individual node may be
accessed by either ordinal index or the node's name or id attributes.Note: Collections in the HTML DOM are assumed to be live meaning that
they are automatically updated when the underlying document is
changed.
http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-75708506
So, HTML Collections are always "in the dom," whereas a nodeList
is a more generic construct that may or may not be in the DOM.
A NodeList object is a collection of nodes...
The NodeList interface provides the abstraction of an ordered
collection of nodes, without defining or constraining how this
collection is implemented. NodeList objects in the DOM are live.
http://www.w3.org/TR/DOM-Level-3-Core/core.html#td-live
Sounds good, right?
A collection is an object that represents a lists of DOM nodes. A
collection can be either live or static. Unless otherwise stated, a
collection must be live.
http://www.w3.org/TR/2012/WD-dom-20120405/#collections
So static collections will be indicated as such in the spec. So, by this logic, document.querySelectorAll()
is a collection, but it is not in the DOM. Because while collections may or may not be live, collections in the DOM must be live... This distinction is not super helpful.
Well, here is a quick method to determine if a collection
is live; it appends a clone of a member of the collection to the DOM
(so it will match the selector), and checks to see if the length changed, and then removes it (so the page is not affected)
DEMO
function isLive(collection) {
if (HTMLCollection.prototype.isPrototypeOf(collection)) return true // HTMLCollections are always live
const length = collection.length;
if (!length) return undefined; // Inconclusive
const el = collection.item(0);
const parent = el.parentNode;
const clone = el.cloneNode();
clone.style.setProperty('display', 'none', 'important');
parent.appendChild(clone);
const live = collection.length !== length;
parent.removeChild(clone);
return live;
}
const divs1 = document.getElementsByClassName('c');
const divs2 = document.getElementsByTagName('span');
const divs3 = document.getElementsByName('notFound');
const divs4 = document.querySelectorAll('.c');
console.log("document.getElementsByClassName('c'):", divs1.toString()); // [object HTMLCollection]
console.log("document.getElementsByTagName('notFound'):", divs2.toString()); // [object HTMLCollection]
console.log("document.getElementsByName('notFound'):", divs3.toString()); // [object NodeList]
console.log("document.querySelectorAll('.c'):", divs4.toString()); // [object NodeList]
console.log('isLive(divs1)', isLive(divs1)); // true
console.log('isLive(divs2)', isLive(divs2)); // true
console.log('isLive(divs3)', isLive(divs3)); // undefined
console.log('isLive(divs4)', isLive(divs4)); // false
<html>
<body>
<div>
<div class="c">C1</div>
<div class="c">C2</div>
</div>
<div>
<div class="c">C3</div>
<div class="c">C4</div>
</div>
</body>
</html>
Live and static DOM elements
You're misunderstanding what's live/static: It's the collection that's live or static, not the elements in the collection.
If you do:
const spans = document.getElementsByTagName("span");
and later, you add a new span
to the DOM, the value of spans.length
will increase because that new span is added to the collection; HTMLCollection
instances are live.
But if you do:
const spans = document.querySelectorAll("span");
and later, you add a new span
to the DOM, the value of spans.length
will not increase, because the new span isn't added to the list; the NodeList
from querySelectorAll
is static.
The elements are live either way.
Example:
const coll = document.getElementsByTagName("span");const list = document.querySelectorAll("span");console.log("(before) coll.length = " + coll.length);console.log("(before) list.length = " + list.length);
console.log("Adding another span");document.body.appendChild( document.createElement("span"));console.log("(after) coll.length = " + coll.length);console.log("(after) list.length = " + list.length);
<span></span>
querySelector() return static node list or live node list
querySelectorAll
returns a static node list, whereas (say) getElementsByTagName
returns a live node list. It's the list that's static or live, not the nodes/elements on the list.
An element returned by querySelector
is the element in the DOM (as are the elements in the list from querySelectorAll
). They're "live," not snapshots or clones — e.g., if they're changed, you can see those changes via the reference you have from querySelector
/querySelectorAll
.
Example:
const element = document.querySelector("input");const staticList = document.querySelectorAll("input");const liveList = document.getElementsByTagName("input");
element.addEventListener("input", () => { // Both of these are references to the live node in the DOM console.log(element.value); console.log(staticList[0].value);});
document.getElementById("del").addEventListener("click", () => { // Removing the input document.body.removeChild(document.querySelector("input")); // It's still on the static list console.log("staticList.length = " + staticList.length); // 1 // It's off the live list now console.log("liveList.length = " + liveList.length); // 0 // Since we still have a reference to it, we can still see it's value console.log(element.value); // "what you typed"});
Type something: <input type="text"><br>then click this: <button id="del" type="button">Delete</button>
How to distinguish between live and non-live NodeList collections?
The NodeList
interface is agnostic of its dead or live status.
interface NodeList {
Node item(in unsigned long index);
readonly attribute unsigned long length;
};
It only contains a property length
, and a method item
so I'm afraid it's currently not possible to determine if an object is live without manipulating the DOM and seeing the effects.
Javascript Live Nodelist Loop
One approach that was used in the past is to loop over the collection backwards, that way if an item is removed it doesn't affect the position of any of the remaining elements:
var hotElements = document.getElementsByClassName("hot"),
i;
for (i = hotElements.length - 1; 0 <= i; i--) {
hotElements[i].className = "pink";
}
One advantage to this approach over the while(hotElements.length > 0)
solution that NikxDa proposed is that it doesn't rely on elements getting removed if you were conditionally applying the new className
instead of doing to every element.
You could also convert the live node collection to a real array that will not change.
Using ES2015 it is quite easy to do using the spread syntax:
var hotElements = document.getElementsByClassName("hot");
[...hotElements].forEach(function (element) {
element.className = "pink";
});
You can also do it in ES5 with a little more code:
var hotElements = document.getElementsByClassName("hot");
Array.prototype.slice.call(hotElements).forEach(function (element) {
element.className = "pink";
});
Using slice
to convert to an array won't work in IE < 9... but at this point in time that probably isn't a concern.
One more approach would be to use querySelectorAll. It returns a non-live NodeList, so if you used it to find the elements you could still loop over it forwards:
var hotElements = document.querySelectorAll(".hot"),
i,
len = hotElements.length;
for (i = 0; i < len ; i++) {
hotElements[i].className = "pink";
}
Interesting related article: A comprehensive dive into NodeLists, Arrays, converting NodeLists and understanding the DOM
Related Topics
How to Run an .Exe or .Bat File on 'Onclick' in Html
Can JavaScript Read the Source of Any Web Page
Do You Need Text/JavaScript Specified in Your ≪Script≫ Tags
Get City Name Using Geolocation
Change :Hover CSS Properties With JavaScript
How to Prevent Iframe from Redirecting Top-Level Window
Does Html5/Canvas Support Double Buffering
Jquery Click Not Working For Dynamically Created Items
Return HTML Content as a String, Given Url. JavaScript Function
Trigger Change Event When the Input Value Changed Programmatically
Strip HTML from Text JavaScript
Populate One Dropdown Based on Selection in Another
Onchange Event on Input Type=Range Is Not Triggering in Firefox While Dragging