웹 개발/웹

HTMLCollection과 NodeList 차이 이해하기

곤이씨 2021. 3. 7. 16:16

안녕하세요 :)

 

 이번에는 querySelectorAll나 getElementsByClassName과 같이 여러 개의 요소 노드 객체를 반환할 때 사용되는 객체인 HTMLCollection과 NodeList의 차이에 대해 알아보도록 하겠습니다.

 

 

 먼저, HTMLCollection과 NodeList는 모두 유사 배열 객체이면서 이터러블입니다. 따라서 둘 다 length 프로퍼티를 가지므로 객체를 배열처럼 접근할 수 있고 반복문을 돌 수 있습니다. 그러나 유사 배열 객체이기 때문에 자바스크립트에서 제공하는 배열 객체의 메소드는 사용할 수 없습니다. (ex. map, forEach, reduce 등등)

 

 

 다음으로는 HTMLCollection과 NodeList에 대해 하나씩 살펴보도록 하겠습니다.

 

 

HTMLCollection

 getElementsByTagname, getElementsByClassName 메서드가 반환하는 HTMLCollection 객체는 노드 객체의 상태 변화를 실시간으로 반영하는 살아있는 live DOM 컬렉션 객체입니다. 여기서 '살아있다'라는 의미는 객체가 스스로 실시간 노드 객체의 상태 변경을 반영함을 의미합니다. 간단한 예시를 보며 이해해봅시다.

 

<!DOCTYPE html>
<html lang="kr">
  <head>
    <meta charset="UTF-8" />
    <title>test</title>
  </head>
    <body>
    <div id="app">
      <h1>test</h1>
      <div class="greeting">Hello</div>
    </div>
  </body>
  <script>
    const $app = document.getElementById('app');
    const $greeting = document.getElementsByClassName('greeting');
    console.log($greeting, $greeting.length); // HTMLCollection [div.greeting] 1
    $app.insertAdjacentHTML('beforeend', '<div class="greeting">Hello</div>');
    console.log($greeting, $greeting.length); // HTMLCollection(2) [div.greeting, div.greeting] 2
  </script>
</html>

 

 처음 greeting이라는 class 명을 가진 요소는 하나 밖에 없습니다. 따라서 첫번째 console.log는 길이가 1인 HTMLCollection을 출력하죠. 그러나 이후에 greeting이라는 class 명을 가진 요소를 추가하면 console.log는 길이가 2인 HTMLCollection를 출력합니다. 우리는 분명히 $greeting을 const로 선언하였습니다. 그리고 $greeting을 재선언하거나 재할당하지도 않았죠. 그럼에도 값이 변경되었습니다. 이는 HTMLCollection이 live 객체이기 때문에 요소 노드의 추가나 삭제를 바로 반영해주기 때문입니다.

 

 

 

 

NodeList

 querySelectorAll 등의 메서드가 반환하는 NodeList 객체는 노드 객체의 상태 변화를 반영하지 않는 non-live DOM 컬렉션 객체입니다. NodeList는 앞의 HTMLCollection과 다르게 노드가 변경되도 그 상태를 반영하지 않습니다. 마찬가지로 예시를 보면서 이해해봅시다.

<!DOCTYPE html>
<html lang="kr">
  <head>
    <meta charset="UTF-8" />
    <title>test</title>
  </head>
  <body>
    <div id="app">
      <h1>test</h1>
      <div class="greeting">Hello</div>
    </div>
  </body>
  <script>
    const $app = document.getElementById('app');
    const $greeting = document.querySelectorAll('.greeting');
    console.log($greeting, $greeting.length); // NodeList [div.greeting] 1
    $app.insertAdjacentHTML('beforeend', '<div class="greeting">Hello</div>');
    console.log($greeting, $greeting.length); // NodeList [div.greeting] 1
  </script>
</html>

 

 이전 예시와 다르게 $greeting에 querySelectorAll를 사용하여 NodeList를 저장하였습니다. $greeting은 NodeList 객체이기 때문에 노드 상태가 변경되도 이전과 길이가 같은 NodeList를 출력합니다. 따라서 NodeList 객체는 대부분의 경우 노드 객체의 상태 변경을 실시간으로 반영하지 않고 과거의 정적 상태를 유지하는 non-live 객체로 동작합니다. 하지만 childNodes 프로퍼티가 반환하는 NodeList 객체는 HTMLCollection 객체와 같이 실시간으로 노드 객체의 상태 변경을 반영하는 live 객체로 동작합니다.

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="index.css" />
    <title>Document</title>
  </head>
  <body id="app">
    <ul id="students">
      <li class="frontend">Gonnie</li>
      <li class="frontend">Poco</li>
    </ul>
  </body>
  <script>
    const $students = document.getElementById("students");
    const childNodes = $students.childNodes;

    console.log(childNodes instanceof NodeList); // true
    console.log(childNodes);
    // NodeList(5) [text, li.frontend, text, li.frontend, text]
    // childNodes는 요소노드 뿐만아니라 공백 텍스트 노드(엔터 키)도 포함되어 있다.

    for (let i = 0; i < childNodes.length; i++) {
      // removeChild 메서드가 호출될 때마다 NodeList live 객체인 childNodes가 실시간으로 변경된다.
      // 따라서 첫 번째, 세 번째, 다섯 번째 요소만 삭제된다.
      $students.removeChild(childNodes[i]);
    }

    console.log(childNodes); // NodeList(2) [li.frontend, li.frontend]
  </script>
</html>

 

또한, NodeList는 HTMCollection과 다르게 NodeList.prototype.forEach 메서드를 상속받아 사용할 수 있습니다. 그러나 forEach 외의 Array.prototype에서 제공하는 map, reduce, filter 등의 메서드는 사용할 수 없습니다.

 

 

따라서 HTMLCollection과 NodeList 모두 편리하게 사용하기 위해서는 배열로 만들어줘야 합니다. 특히 HTMLCollection과 같은 live 객체는 반복문을 순회하면서 노드가 변경되는 경우, 개발자의 의도와는 다른 결과가 발생할 수 있으므로 배열로 바꾸어 사용하는 것이 바람직합니다.

 

 

두 객체를 배열로 만드는 방법은 Array.from과 스프레드 연산자를 이용한 방법이 있습니다.

const $greeting = document.querySelectorAll('.greeting');
Array.from($greeting);
[...$greeting];

 

 

 

마지막으로 HTMLCollection과 NodeList를 정리한 표입니다.

 

 

 

 

 둘 다 자주 사용되는 DOM 컬렉션 객체이므로 차이를 명확하게 알아둔다면 개발할 때 발생하는 에러에 대해 빠르게 대응할 수 있을 것 입니다. 이상으로 HTMLCollection과 NodeList 차이 알아보기 포스팅을 마치겠습니다. 감사합니다 😊