개발을 하다보면 특정 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>
getElementById
는 DOM에서 id속성을 갖는 요소를 찾아서 반환하는 메서드 const getElementById = document.getElementById('selectBox')
qureySelector
는 CSS선택자를 사용해서 요소를 찾는 메소드이다 const querySelector = document.querySelector('#selectBox')
위 두가지 모두 같은 결과를 반환 한다.
그렇다면 그 <select>
태그 안에 있는 <option>
들을 불러 오려면?
const getElementById = document.getElementById('selectBox')
console.log('getElementById.children = ',getElementById.children);
const querySelector = document.querySelector('#selectBox')
console.log('querySelector.children = ',querySelector.children);
두 메소드 모두 children을 이용해서 쉽게 가져올 수 있었다.
혹은
const querySelectorAll = document.querySelectorAll('[name=selectOption]')
querySelector
의 요소 안에 있는 요소 선택 그리고 또 querySelector
를 사용하면 특정요소 안에 있는 요소를 선택할 수 있다.
~~~.(#selectBox .firstClass)
const querySelector = document.querySelector('#selectBox .firstClass')
const querySelectorAll = document.querySelectorAll('#selectBox .firstClass')
반면에 getElementById
를 사용할 경우 단 하나의 요소만 반환하기 때문에 요소 안에 있는 요소를 반환하기 때문에 null
만 반환하다.
속도 측면에서는 일반적으로 querySelector
가 더 느리다고 알려져 있다. 왜 그럴까?
MDN 문서에 따르면 getElementById
와 querySelector
는 깊이 우선 탐색 방법을 사용하고 있다. 두 메소드 모두 원하는 노드를 찾는 순간 탐색이 종료된다.
깊이 우선 탐색 방법 (DFS, Depth-First Search)
트리나 그래프에서 한 루트로 탐색하다가 특정 상황에서 최대한 깊이 들어가서 확인한 후 다시 돌아가 다른 루트로 탐색하는 방식
querySelector
getElementById
getElementById
에는 HashTable 자료구조가 적용되어 있어서 key,value를 1:1로 매핑되어 key가 입력되면 곧바로 value를 반환해주기에 매우 빠른 탐색 속도를 보여준다. (우선 탐색)개발 시 이러한 특성을 고려하여 상황에 맞게 사용을 할 필요가 있다.
예를 들어 id
값이 고유하게 지정되어 있고 해당 요소를 자주 사용할 경우에는 getElementById
를 사용하는 것이 더 좋을 것 갘다. 반면 CSS 선택자를 사용하여 요소를 선택할 경우 querySelector
를 사용하는게 더 편할 것 같다.
위에서 작성하다보니 HTMLCollection
과 NodeList
를 볼 수 있는데, 이 둘은 무엇일까?
HTMLCollection
과 NodeList
는 DOM API가 여러가지 결과값을 반환하기 위한 컬렉션 객체이다. 이 둘은 모두 유사 배열 객체이고 iterable이다.
예제 코드
<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>
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 (let i = getElementsByClassName.length -1 ; i >= 0; i--) {
getElementsByClassName[i].className = 'red'
}
let i = 0;
while (getElementsByClassName.length > i) {
getElementsByClassName[i].className = 'red';
}
[...getElementsByClassName].forEach(e => e.className = 'red')
HTMLCollection 객체의 문제를 해결하기위해 nodeList
를 반환하는 querySelectorAll
메소드를 사용하는 방법도 있다.
앞서 말한거 처럼 nodeList
는 실시간으로 객체의 상태 변경을 반영하지 않는다.
for (let i = querySelectorAll.length -1 ; i >= 0; i--) {
querySelectorAll[i].className = 'red'
}
querySelectorAll.forEach(e => e.className = 'red')
이처럼 nodeList
객체는 실시간으로 반영하지 않고 과거의 정적 상태를 유지하는 non-live 객체로 동작한다.
하지만 childNodes
property가 반환하는 NodeList
객체는 HTMLCollection 객체와 동일하게 실시간으로 변경을 반영하기에 주의가 필요하다.
그렇기에 노드 객체의 상태 변경과 상관없이 안전하게 사용하려면 HTMLCollection
이나 NodeList
객체를 배열로 변환하여 사용하는 것이 좋다.