bsky
bsky

Reputation: 20242

getElementsByTagName returns array with null elements

I have this Tampermonkey script:

// ==UserScript==
// @name         Remove strong tags
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  remove answers
// @author       You
// @match        http://*/*
// @grant        GM_log
// @run-at       document-end
// ==/UserScript==

setTimeout((function() {
    'use strict';

    var allStrong = document.getElementsByTagName("strong");
    GM_log(allStrong);

    for (var i=0, max=allStrong.length; i < max; i++) {
        var strong = allStrong.item(i);
        GM_log(strong);
        if(strong && strong.parentNode.tagName == "LI") {
            strong.parentNode.appendChild(strong.firstChild);
            strong.parentNode.removeChild(strong);
        }
    }
})(), 5000);

Out of 8 strong tags that fit the criteria, it only replaces 4.

This is printed in the Chrome console.

enter image description here

The array when first printed has 8 elements, but then the last 4 elements are printed as undefined.

Why is this happening?

I don't think the problem comes from the code being executed before the full page load, because I put a large timeout.

This is the page where I'm running it.

Upvotes: 0

Views: 341

Answers (2)

pwolaq
pwolaq

Reputation: 6381

getElementsByTagName returns a so-called live NodeList, not a static Array - it means that it contains up-to-date results of your query.

So, after each of your iterations, one of the elements is removed as it is no longer <strong> element.

You have 2 choices here:

  • Transform NodeList into normal Array (eg. by using Array.from)
  • Always access first element of your NodeList

Upvotes: 4

Sheraff
Sheraff

Reputation: 6752

The first printout shows 8 elements because it's what it is at that point. But it is an HTMLCollection, and in the HTML DOM it is live; it is automatically updated when the underlying document is changed.

The for loop removes some of these <strong> nodes as it goes (strong.parentNode.removeChild(strong);), but only evaluates the length of the collection at the beginning (max=allStrong.length). So it will still run 8 times, but by the time it reaches half, it iterates over nothing.

When you expand the first line of logs, Chrome evaluates your live HTMLCollection, which by that point only contains 4 elements, so it shows only 4. The little (i) at the end of the line tells you when this expression was evaluated.

If you want to see all this play out in real time, instead of putting it in a setTimeout, I'd suggest adding the following statement in your code, wherever you want to start looking at it step by step:

debugger;

Upvotes: 2

Related Questions