Search code examples
javascriptdomfor-in-loop

What is causing my simple JavaScript for-in loops to not colour the background of the specified HTML elements?


I'm in the process of learning JavaScript and have come across an issue I don't understand.

I have a simple webpage I'm using for testing, and here I'm trying to use for-in loops to change the background colour of all h1 elements to red, and all h2 elements to blue.

var h1 = document.getElementsByTagName("h1");
var h2 = document.getElementsByTagName("h2");

for (i in h1) {
    h1[i].style.backgroundColor = "red";
}

for (i in h2) {
    h2[i].style.backgroundColor = "blue";
}

When I run this code, the background of all h1 elements turn red as intended. However, none of the h2 elements change at all.

I get an error in the console that says: TypeError: h1[i].style is undefined
I don't understand this however as the code it's referencing seems to run just fine.

If I comment out h1[i].style.backgroundColor = "red"; and refresh the page, all of the h2 elements are changed to blue.

I then get an error in the console similar to the first one: TypeError: h2[i].style is undefined

Does anybody know what's going on any why my code isn't running both these loops? It looks like it runs fine until the end of the first loop, then errors out and stops running so doesn't run the second loop.

Thanks in advance!


Solution

  • That is because when you're using a for...in loop, you are looping through all properties of an object. document.getElementsByTagName() returns a HTMLCollection, which contains the array of matched HTML elements and a length property (the latter belongs to its prototype).

    In the first for loop, you will eventually attempt to access h1.length, which is not an element. Therefore, you will run into an error which stops the script from executing further. A fix will be to add a check for Object.hasOwnProperty(i) before setting the styles, ensuring that we are only using properties on the object itself and not its prototype:

    var h1 = document.getElementsByTagName("h1");
    var h2 = document.getElementsByTagName("h2");
    
    for (i in h1) {
      if (h1.hasOwnProperty(i))
        h1[i].style.backgroundColor = "red";
    }
    
    for (i in h2) {
      if (h1.hasOwnProperty(i))
        h2[i].style.backgroundColor = "blue";
    }
    <h1>h1 number 1</h1>
    <h1>h1 number 2</h1>
    <h1>h1 number 3</h1>
    
    <h2>h2 number 1</h2>
    <h2>h2 number 2</h2>
    <h2>h2 number 3</h2>

    Alternatively, a better way is to simply use a forEach on the NodeList returned by document.querySelectorAll().

    var h1 = document.querySelectorAll("h1");
    var h2 = document.querySelectorAll("h2");
    
    h1.forEach(el => el.style.backgroundColor = 'red');
    h2.forEach(el => el.style.backgroundColor = 'blue');
    <h1>h1 number 1</h1>
    <h1>h1 number 2</h1>
    <h1>h1 number 3</h1>
    
    <h2>h2 number 1</h2>
    <h2>h2 number 2</h2>
    <h2>h2 number 3</h2>