본문 바로가기

JavaScript/모던 자바스크립트 튜토리얼

getElement*, querySelector*로 요소 검색하기

document.getElementById 혹은 id를 사용해 요소 검색하기

요소에 id 속성이 있으면 위치에 상관없이 메서드 document.getElementById(id)를 이용하여 접근할 수 있습니다.

 

<div id="elem">
  <div id="elem-content">Element</div>
</div>

<script>
  // 요소 얻기
  let elem = document.getElementById('elem');

  // 배경색 변경하기
  elem.style.background = 'red';
</script>

 

그런데 아래와  같이 id 속성 값을 그대로 딴 전역 변수를 이용해 접근할 수도 있습니다.

 

<div id="elem">
  <div id="elem-content">Element</div>
</div>

<script>
  // 변수 elem은 id가 'elem'인 요소를 참조합니다.
  elem.style.background = 'red';

  // id가 elem-content인 요소는 중간에 하이픈(-)이 있기 때문에 변수 이름으로 쓸 수 없습니다.
  // 이럴 땐 대괄호(`[...]`)를 사용해서 window['elem-content']로 접근하면 됩니다.
</script>

 

그런데 이렇게 요소 id를 따서 자동으로 선언된 전역 변수는 동일한 이름을 가진 변수가 선언되면 무용지물이 됩니다.

 

<div id="elem"></div>

<script>
  let elem = 5; // elem은 더이상 <div id="elem">를 참조하지 않고 5가 됩니다.

  alert(elem); // 5
</script>

 

따라서 id를 따서 만들어진 전역 변수를 요소 접근시 사용하면 안됩니다. id에 대응하는 전역변수는 명세서의 내용을 구현해 만들어진 것으로 표준이긴 하지만 하위 호환성을 위해 남겨둔 동작입니다.

 

브라우저는 스크립트의 네임 스페이스와 DOM의 네임 스페이스를 함께 사용할 수 있도록 해서 개발자의 편의를 도모합니다. 그런데 이런 방식은 스크립트가 간단할 땐 괜찮지만, 이름이 충돌할 가능성이 있기 때문에 추천하는 방식은 아닙니다. HTML을 보지 않은 상황에서 코드만 보고 변수의 출처를 알기 힘들다는 단점도 있습니다. 본 튜토리얼에선 간결성을 위해 요소의 출처가 명확한경우 id를 사용해 직접 접근하는 방법을 사용합니다. 실무에서는 document.getElementById를 사용합니다. 

 

id는 중복되어선 안됩니다. 문서 내의 요소에서 id 속성값은 유일무이해야합니다. 같은 id를 가진 요소가 여러개 있으면 document.getElementById같이 id를 이용해 요소를 검색하는 메서드의 동작이 예측 불가능해집니다. 

 

anyNode.getElementById가 아닌 document.getElementById

-> getElementById는 document 객체를 대상으로 해당 id를 가진 요소 노드를 찾아 줍니다. 문서 노드가 아닌 다른 노드엔 호출할 수 없습니다.

 

querySelectorAll

elem.querySelectorAll(css)는 다재다능한 요소 검색 메서드입니다. 이 메서드는 elem의 자식 요소 중 주어진 CSS 선택자에 대응하는 요소 모두를 반환합니다. 아래 예시는 마지막 <li> 요소 모두를 반환합니다.

 

<ul>
  <li>1-1</li>
  <li>1-2</li>
</ul>
<ul>
  <li>2-1</li>
  <li>2-2</li>
</ul>
<script>
  let elements = document.querySelectorAll('ul > li:last-child');

  for (let elem of elements) {
    alert(elem.innerHTML); // "1-2", "2-2"
  }
</script>

 

querySelectorAll은 CSS 선택자를 활용할 수 있다는 점에서 아주 유용합니다. 가상 선택자도 사용할 수 있습니다. querySelectorAll에는 :hover나 :active 같은 CSS 선택자의 가상 클래스(pseudo-class)도 사용할 수 있습니다. document.querySelectorAll(':hover')을 사용하면 마우스 포인터가 위에 있는(hover 상태인) 요소 모두를 담은 컬렉션이 반환됩니다. 이때 컬렉션은 DOM 트리 최상단에 위치한 <html> 부터 가장 하단의 요소 순으로 채워집니다. 

 

querySelector

elem.querySelector(css)는 주어진 CSS 선택자에 대응하는 요소 중 첫 번째 요소를 반환합니다. elem.querySelectoAll(css)[0]과 동일하죠. elemQuerySelectorAll(css)[0]은 선택자에 해당하는 모든 요소를 검색해 첫 번째 요소만을 반환하고, elem.querySelector는 해당하는 요소를 찾으면 검색을 멈춘다는 점에서 차이가 있습니다. elem.querySelector가 더 빠른 이유이죠. 

 

지금까지 소개한 모든 메서드는 DOM 검색에 쓰입니다. elem.matches(css)는 DOM을 검색하는 일이 아닌 조금 다른 일을 합니다. 이 메서드는 요소 elem이 주어진 CSS 선택자와 일치하는지 여부를 판단해줍니다. 일치한다면 true, 아니라면 false를 반환하죠. 요소가 담겨있는 배열 등을 순회해 원하는 요소만 걸러내고자 할 때 유용합니다. 

 

<a href="http://example.com/file.zip">...</a>
<a href="http://ya.ru">...</a>

<script>
  // document.body.children가 아니더라도 컬렉션이라면 이 메서드를 적용할 수 있습니다.
  for (let elem of document.body.children) {
    if (elem.matches('a[href$="zip"]')) {
      alert("주어진 CSS 선택자와 일치하는 요소: " + elem.href );
    }
  }
</script>

closest

closest 메서드는 해당 요소부터 시작해 DOM 트리를 한 단계씩 거슬러 올라가면서 원하는 요소를 찾습니다. CSS 선택자와 일치하는 요소를 찾으면, 검색을 중단하고 해당 요소를 반환합니다.

 

<h1>목차</h1>

<div class="contents">
  <ul class="book">
    <li class="chapter">1장</li>
    <li class="chapter">2장</li>
  </ul>
</div>

<script>
  let chapter = document.querySelector('.chapter'); // LI

  alert(chapter.closest('.book')); // UL
  alert(chapter.closest('.contents')); // DIV

  alert(chapter.closest('h1')); // null(h1은 li의 조상 요소가 아님)
</script>

getElementsBy*

태그나 클래스 등을 이용해 원하는 노드를 찾아주는 메서드도 있습니다. querySelector를 이용하는게 더 편리하고 문법도 짧아서, 요즘은 이런 메서드들을 잘 쓰진 않습니다. 튜토리얼의 완성도를 높이고 오래된 스크립트에서 해당 메서드들을 만날 때 당황하지 않으시길 바라면서 이 메서드들을 잠시 언급하도록 하겠습니다.

- elem.getElementsByTagName(tag): 주어진 태그에 해당하는 요소를 찾고, 대응하는 요소를 담은 컬렉션을 반환합니다. 매개변수 tag에 '*'이 들어가면 '모든 태그'가 검색됩니다.

- elem.getElementsByClassName(className): class 속성 값을 기준으로 요소를 찾고, 대응하는 요소를 담은 컬렉션을 반환합니다. 

- elem.getElementsByName(name): 아주 드물게 쓰이는 메서드로, 문서 전체를 대상으로 검색을 수행합니다. 검색 기준은 name 속성값이고, 이 메서드 역시 검색 결과를 담은 컬렉션을 반환합니다.

 

Live Collection

getElementsBy로 시작하는 모든 메서드는 살아있는 컬렉션을 반환합니다. 문서에 변경이 있을 때마다 컬렉션이 자동으로 갱신되어 최신 상태를 유지합니다. 

 

예시 내엔 스크립트 두 개가 있습니다. 

 

1. 첫 번째 스크립트는 <div>에 상응하는 요소를 담은 컬렉션에 대한 참조를 만듭니다. 스크립트가 실행되는 시점에 이 컬렉션의 길이는 1입니다. 

2. 두 번째 스크립트는 문서에 <div>가 하나 더 추가된 이후에 실행됩니다. 따라서 컬렉션의 길이는 2가 됩니다. 

<div>첫 번째 div</div>

<script>
  let divs = document.getElementsByTagName('div');
  alert(divs.length); // 1
</script>

<div>두 번째 div</div>

<script>
  alert(divs.length); // 2
</script>

 

반면 querySelectorAll은 정적인 컬렉션을 반환합니다. 컬렉션이 한 번 확정되면 더는 늘어나지 않습니다. querySelectorAll을 사용하면 두 스크립트가 동일하게 1을 출력합니다. 

 

<div>첫 번째 div</div>

<script>
  let divs = document.querySelectorAll('div');
  alert(divs.length); // 1
</script>

<div>두 번째 div</div>

<script>
  alert(divs.length); // 1
</script>

 

예시를 통해 두 방식의 차이를 살펴보았습니다. 문서에 새로운 div가 추가되어도 querySelectorAll이 반환한 컬렉션은 이를 반영하지 못합니다.

 

 

요약

아마 실무에선 querySelector나 querySelectorAll을 가장 많이 사용하실 것입니다. getElementBy로 시작하는 메서드는 대개 오래된 스크립트에서 만날 수 있는데, 일부 이 메서드가 꼭 필요한 상황에서 쓰이는 경우도 있습니다. 이 외에 알아두면 좋을 만한 메서드는 아래와 같습니다.

 

- elem.matches(css)는 elem이 해당 CSS 선택자와 일치하는지 여부를 검사합니다. 

- elem.closest(css)는 해당 CSS 선택자와 일치하는 가장 가까운 조상 요소를 탐색합니다. 이때 elem 자기 자신도 검색 대상에 포함됩니다. 

 

위에서 언급하지 않았지만, 노드의 부모-자식 관계를 확인할 수 있도록 도와주는 유용한 메서드가 있습니다. 

- elemA.contains(elemB)는 elemB가 elemA에 속하거나(elemB가 elemA의 후손) elemA == elemB일 때 참을 반환합니다.