Pockets Pockets - 4 months ago 22
Javascript Question

How exactly does getElementsByClassName work in Chrome?, specifically w.r.t. NodeLists & DOMs

All of the following results were obtained using Google Chrome v36 & its console.

While debugging a Wordpress plugin, I discovered that running this little Javascript snippet

console.log(document.getElementsByClassName("switch-tmce"))
console.log(document.getElementsByClassName("switch-tmce").length)


would log the following (expanded after the page finished loading):

[item: function, namedItem: function]
0: a#ninja_forms_field_10-tmce.hide-if-no-js.wp-switch-editor.switch-tmce
1: a#ninja_forms_field_15-tmce.hide-if-no-js.wp-switch-editor.switch-tmce
length: 2
ninja_forms_field_10-tmce: a#ninja_forms_field_10-tmce.hide-if-no-js.wp-switch-editor.switch-tmce
ninja_forms_field_15-tmce: a#ninja_forms_field_15-tmce.hide-if-no-js.wp-switch-editor.switch-tmce
__proto__: HTMLCollection
0


If I adjusted the snippet to wait for the DOM to finish loading like so:

window.addEventListener("DOMContentLoaded", function() {
console.log(document.getElementsByClassName("switch-tmce"))
console.log(document.getElementsByClassName("switch-tmce").length)
}, false);


it would then log the following (expanded after the page finished loading):

[a#ninja_forms_field_10-tmce.hide-if-no-js.wp-switch-editor.switch-tmce, a#ninja_forms_field_15-tmce.hide-if-no-js.wp-switch-editor.switch-tmce, ninja_forms_field_10-tmce: a#ninja_forms_field_10-tmce.hide-if-no-js.wp-switch-editor.switch-tmce, ninja_forms_field_15-tmce: a#ninja_forms_field_15-tmce.hide-if-no-js.wp-switch-editor.switch-tmce, item: function, namedItem: function]
0: a#ninja_forms_field_10-tmce.hide-if-no-js.wp-switch-editor.switch-tmce
1: a#ninja_forms_field_15-tmce.hide-if-no-js.wp-switch-editor.switch-tmce
length: 2
ninja_forms_field_10-tmce: a#ninja_forms_field_10-tmce.hide-if-no-js.wp-switch-editor.switch-tmce
ninja_forms_field_15-tmce: a#ninja_forms_field_15-tmce.hide-if-no-js.wp-switch-editor.switch-tmce
__proto__: HTMLCollection
2


What I'm having trouble understanding is just what exactly is happening here - in particular, why the
length
property only returns "correctly", so to speak, after the DOM loads. I've found this explanation:


It might be, that while calling getElementsByTagName, no input elements exist, but since NodeLists are dynamic, when the document loads, elements will contain all 28 inputs.


but I'm reading that as saying that getElementsByTagName parses the NodeList until it can parse the DOM, and can only return the
length
property when parsing the DOM, which doesn't seem right to me, since it still has finite countable elements.

Moreover, there's also the matter of
[item:function, namedItem:function]
changing to
[a.someClass.someOtherClass, a.someclass.someOtherClass]
, which the above doesn't explain.

Hence my question: What exactly is happening under the hood with
getElementsByClassName
that the
length
property is not set (does not exist?) until after the DOM loads, despite that the prototype remains the same? How is this related to/why does the output change from
[item:function, namedItem:function]
to
[a.someClass.someOtherClass, a.someclass.someOtherClass]
?

Answer

As you can see, getElementsByClassName returns a HTMLCollection - that is, a live reference for your query.

What's happening is that you're expanding the live reference in the console after the DOM is ready, but logging it before the DOM is ready. Because it is a live reference, by expanding when the DOM is ready, when the HTMLCollection references the object in the memory, it sees that the DOM is ready and pulls from the finished DOM.

If, however, you expanded the reference while having paused the execution of the Javascript (this can be done with something like debugger), this is what you would get:

[item: function, namedItem: function]
    length: 0
    __proto__: HTMLCollection
0

because the DOM would not yet be ready then.

That's why the first logged reference showed up as [item: function, namedItem: function], because when you logged it, the DOM wasn't ready. Once the DOM was ready, it was logged as [a.someClass.someOtherClass, a.someClass.someOtherClass].

The length output, though, is just a number, not an object reference, and logs as is, which is why it prints 0 before the DOM is ready and 2 after it is - because that's exactly what's happening, since there are no DOM elements before the DOM is ready.

Comments