[Javascript] querySelector와 getElementById 그리고 HTMLCollection과 NodeList

Castle_Junny·2023년 3월 19일
0

자바스크립트

목록 보기
1/2
post-thumbnail

개발을 하다보면 특정 node를 선택해서 동적으로 작업을 하는 경우가 있다.
이때 사용하게 되는게 quertSelector, getElementById를 사용하게 된다. 근데 항상 사용할 때마다 이 둘은 무슨 차이가 있는지 궁금해서 궁금증을 해결해 보았다.

예시 코드

<div class="container" >
    <select id="selectBox">
        <option name="selectOption" class="firstClass" value="test1">test1</option>
        <option name="selectOption" value="test2">test2</option>
        <option name="selectOption" value="test3">test3</option>
        <option name="selectOption" value="test4">test4</option>
    </select>
</div>

1. select box를 선택 할 때

  • getElementById 는 DOM에서 id속성을 갖는 요소를 찾아서 반환하는 메서드
  const getElementById =  document.getElementById('selectBox')
  • qureySelector 는 CSS선택자를 사용해서 요소를 찾는 메소드이다
  const querySelector =  document.querySelector('#selectBox')

위 두가지 모두 같은 결과를 반환 한다.
그렇다면 그 <select> 태그 안에 있는 <option> 들을 불러 오려면?

2. select box 내 option들을 선택할 때

  • getElementById
  const getElementById =  document.getElementById('selectBox')
  console.log('getElementById.children = ',getElementById.children);
  • querySelector
  const querySelector =  document.querySelector('#selectBox')
  console.log('querySelector.children = ',querySelector.children);

두 메소드 모두 children을 이용해서 쉽게 가져올 수 있었다.

혹은

  • querySelectorAll
    querySelectorAll을 이용해서 원하는 요소를 전부 찾을 수 있다.
const querySelectorAll = document.querySelectorAll('[name=selectOption]')

  • querySelector의 요소 안에 있는 요소 선택

그리고 또 querySelector 를 사용하면 특정요소 안에 있는 요소를 선택할 수 있다.
~~~.(#selectBox .firstClass)

const querySelector = document.querySelector('#selectBox .firstClass')
const querySelectorAll = document.querySelectorAll('#selectBox .firstClass')

반면에 getElementById를 사용할 경우 단 하나의 요소만 반환하기 때문에 요소 안에 있는 요소를 반환하기 때문에 null만 반환하다.

3. 속도 측면

속도 측면에서는 일반적으로 querySelector가 더 느리다고 알려져 있다. 왜 그럴까?

MDN 문서에 따르면 getElementByIdquerySelector깊이 우선 탐색 방법을 사용하고 있다. 두 메소드 모두 원하는 노드를 찾는 순간 탐색이 종료된다.

깊이 우선 탐색 방법 (DFS, Depth-First Search)
트리나 그래프에서 한 루트로 탐색하다가 특정 상황에서 최대한 깊이 들어가서 확인한 후 다시 돌아가 다른 루트로 탐색하는 방식

3.1 탐색 시 각각의 메소드의 차이

  • querySelector
    찾고자 하는 요소의 첫번째 node를 찾아서 반환하므로 타겟 node 발견 시 바로 탐색을 종료한다.
  • getElementById
    getElementById에는 HashTable 자료구조가 적용되어 있어서 key,value를 1:1로 매핑되어 key가 입력되면 곧바로 value를 반환해주기에 매우 빠른 탐색 속도를 보여준다. (우선 탐색)
    만약 찾지 못했을 시 DFS를 통해 node를 찾는다.

개발 시 이러한 특성을 고려하여 상황에 맞게 사용을 할 필요가 있다.
예를 들어 id값이 고유하게 지정되어 있고 해당 요소를 자주 사용할 경우에는 getElementById를 사용하는 것이 더 좋을 것 갘다. 반면 CSS 선택자를 사용하여 요소를 선택할 경우 querySelector를 사용하는게 더 편할 것 같다.

4. HTMLCollection과 NodeList

위에서 작성하다보니 HTMLCollectionNodeList를 볼 수 있는데, 이 둘은 무엇일까?

HTMLCollectionNodeList는 DOM API가 여러가지 결과값을 반환하기 위한 컬렉션 객체이다. 이 둘은 모두 유사 배열 객체이고 iterable이다.

  • HTMLCollection
    Html collection객체 반환
    상태 변화가 실시가으로 반영되는 살아있는(live) 객체이다.
  • NodeList
    DOM collection 객체를 반환
    노드 객체의 상태 변화를 실시간으로 반영하지 않고 과거의 정적 상태를 유지하는 non-live 객체로 동작하지만 경우에 따라 live로 동작한다.

예제 코드

<style>
    .blue {color: blue;}
    .red {color: red;}
</style>
...
<ul>
    <li class="blue">test1</li>
    <li class="blue">test2</li>
    <li class="blue">test3</li>
</ul>

4.1 HTMLCollection

getElementsByClassName로 'blue' class를 HTMLCollection 으로 가져와서 반복문을 통해 class 명을 'red'로 변경해 보면 아래와 같은 결과를 보여준다.

    const getElementsByClassName = document.getElementsByClassName('blue');

    for (let i = 0; i < getElementsByClassName.length; i++) {
        getElementsByClassName[i].className = 'red'
    }

이유는 HTMLCollection가 살아 있기 때문에 반복문이 돌 때마다 반영되기 떄문에 제대로 반영되지 않았다.
이것을 보기 위해서 length를 앞 뒤로 찍어보면 class명이 변경될 때마다 length가 줄어드는 것을 볼 수 있다.

   for (let i = 0; i < getElementsByClassName.length; i++) {
        console.log('[before] i = %i, length = %i',i,getElementsByClassName.length)
        getElementsByClassName[i].className = 'red'
        console.log('[after] i = %i, length = %i',i,getElementsByClassName.length)
    }

이에 대한 해결책으로는 다음 세가지 방법이 있다.

  • 역방향 for 문
    for (let i = getElementsByClassName.length -1 ; i >= 0; i--) {
        getElementsByClassName[i].className = 'red'
    }
  • while문
   let i = 0;
    while (getElementsByClassName.length > i) {
        getElementsByClassName[i].className = 'red';
    }
  • 고차함수 (map, forEach, filter,reduce 등)
    스프레드 문법 혹은 Array.from 으로 배열화 필요
    [...getElementsByClassName].forEach(e => e.className = 'red')

4.2 NodeList

HTMLCollection 객체의 문제를 해결하기위해 nodeList를 반환하는 querySelectorAll메소드를 사용하는 방법도 있다.
앞서 말한거 처럼 nodeList는 실시간으로 객체의 상태 변경을 반영하지 않는다.

  • for 문
 for (let i = querySelectorAll.length -1 ; i >= 0; i--) {
        querySelectorAll[i].className = 'red'
    }
  • 고차함수
    nodeList는 forEach 메소드를 상속받아 바로 사용가능
    querySelectorAll.forEach(e => e.className = 'red')

이처럼 nodeList객체는 실시간으로 반영하지 않고 과거의 정적 상태를 유지하는 non-live 객체로 동작한다.
하지만 childNodes property가 반환하는 NodeList 객체는 HTMLCollection 객체와 동일하게 실시간으로 변경을 반영하기에 주의가 필요하다.

그렇기에 노드 객체의 상태 변경과 상관없이 안전하게 사용하려면 HTMLCollection 이나 NodeList 객체를 배열로 변환하여 사용하는 것이 좋다.

참고

  1. [도서] 모던 자바스크립트
  2. https://yozm.wishket.com/magazine/detail/1803/

0개의 댓글