Unexpected behavior while iterating over JavaScript DOMTokenList

I don’t quite get how iteration over a JavaScript DOMTokenList works. I guess, this is not specific for a DOMTokenList but rather for (many?) JavaScript-Objects. As you can see in the below snippet, I want to remve all classes of a div by iterating over the corresponding DOMTokenList. But only function testC() which first collects the class names in an extra array removes all classes. Functions testA() and testB() will alwyas retain the css class b. Why?

Remarks: I know of course that it’s possible to remove all classes of an object in a better way. However, I stumbled over this for me unexpected behavior and want to understand what I did wrong.

function testA() {
  const element = document.getElementById('foo')
  const elementClassList = element.classList
  elementClassList.forEach(function(key) {
    elementClassList.remove(key)
  })
  console.log(elementClassList)
}

function testB() {
  const element = document.getElementById('bar')
  const elementClassList = element.classList
  const iterator = elementClassList.values()
  for (let value of iterator) {
    elementClassList.remove(value)
  }
  console.log(elementClassList)
}

function testC() {
  const element = document.getElementById('bat')
  const elementClassList = element.classList
  let classNames = []
  elementClassList.forEach(function(names) {
    classNames.push(names)
  })

  classNames.forEach(function(name) {
    elementClassList.remove(name)
  })

  console.log(elementClassList)
}
div {
  border: solid 1px gray;
  background: #F8F9FA;
  padding: 1rem;
  margin-bottom: 1rem;
}
<!doctype html>
<html lang="de">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>DOMTOKenlost mystery</title>

  <style>

  </style>
</head>

<body>
  <main>

    <div id="foo" class="a b c">
      #Foo<br> CSS-classes: a b c
    </div>
    <div id="bar" class="a b c">
      #Bat<br> CSS-classes: a b c
    </div>
    <div id="bat" class="a b c">
      #Ba<br> CSS-classes: a b c
    </div>
    <hr>
    <button onclick="testA()" type="button">testA()</button>
    <button onclick="testB()" type="button">testB()</button>
    <button onclick="testC()" type="button">testC()</button>
  </main>
  <script>
  </script>
</body>

</html>

>Solution :

This is happening because you are altering the list during the loop. By altering the list you shift the indexes. Lets go over the loop in more detail:

First iteration:

  • Current index: 0
  • Index of a: 0 (matches current index)
  • Index of b: 1
  • Index of c: 2

Second iteration:

  • Current index: 1
  • Index of a: removed
  • Index of b: 0
  • Index of c: 1 (matches current index)

Last iteration:

  • Current index: 2
  • Index of a: removed
  • Index of b: 0
  • Index of c: removed

As you can see the only items that get removed are the ones where the current index matches the elements index.

To solve this issue you can make a copy of the classList and loop over it.

function testA() {
  const element = document.getElementById('foo')
  const elementClassList = [...element.classList]; // Make a copy.
  elementClassList.forEach(function(key) {
    element.classList.remove(key) // Remove from the original.
  })
  console.log(element.classList)
}
<div id="foo" class="a b c">
  #Foo<br> CSS-classes: a b c
</div>
<hr>
<button onclick="testA()" type="button">testA()</button>

Leave a Reply