지식
JavaScript
DOM 요소 선택 — querySelector와 선택 API 완전 정복
querySelector · querySelectorAll · getElementById · getElementsBy* 의 차이, 정적 vs 실시간 컬렉션, :scope 선택자, 성능 비교를 실무 패턴과 함께 정리합니다.
지난 글에서 Node, Element, Document, Text 인터페이스를 살펴봤습니다. 이번에는 DOM에서 원하는 요소를 정확히 찾는 선택 API를 정리합니다.
querySelector / querySelectorAll
CSS 선택자를 그대로 사용할 수 있는 가장 유연한 API입니다.
// 단일 요소 — 첫 번째 일치 (없으면 null)
const btn = document.querySelector('button.primary');
const header = document.querySelector('#app > header');
// 여러 요소 — 정적 NodeList
const items = document.querySelectorAll('.list-item');
const checked = document.querySelectorAll('input[type=checkbox]:checked');
const firstTwo = document.querySelectorAll('li:nth-child(-n+2)');
// NodeList → Array
const arr = [...items];
Array.from(items);
:scope — 컨텍스트 기준 선택
element.querySelectorAll(sel)은 전역 문서에서 선택자를 평가한 뒤 결과를 element 하위로 필터링합니다. :scope는 기준 요소를 명시적으로 가리킵니다.
const list = document.querySelector('.nav-list');
// ⚠ 의도치 않은 동작 — 문서 전체에서 .nav-item > a 찾고 list 하위 필터
const links = list.querySelectorAll('.nav-item > a');
// ✅ :scope — list의 직접 자식 .nav-item > a
const links2 = list.querySelectorAll(':scope > .nav-item > a');
getElementById 계열
// getElementById — document 전용, 가장 빠름
const app = document.getElementById('app'); // id 선택자 없이 순수 id
// getElementsByTagName — 실시간 HTMLCollection
const divs = document.getElementsByTagName('div'); // '*'로 전체 요소
const svgs = document.getElementsByTagNameNS(
'http://www.w3.org/2000/svg', 'circle'
);
// getElementsByClassName — 실시간 HTMLCollection
const cards = document.getElementsByClassName('card');
// 여러 클래스는 공백으로 구분
const activeCards = document.getElementsByClassName('card active');
// element에서도 사용 가능
const nav = document.querySelector('nav');
nav.getElementsByTagName('a'); // nav 하위의 a만
실시간 컬렉션 vs 정적 컬렉션
| HTMLCollection (getElementById 계열) | NodeList (querySelectorAll) | |
|---|---|---|
| DOM 변경 반영 | 실시간 | 정적 (스냅샷) |
| 포함 노드 | Element만 | 설정에 따라 (childNodes는 실시간) |
| 성능 | 빠름 | 약간 느림 |
| 위험성 | 순회 중 DOM 변경 시 버그 | 안전 |
// 실시간 컬렉션 안전하게 순회하기
const divs = document.getElementsByTagName('div');
[...divs].forEach(div => div.classList.add('processed'));
// Array로 변환 후 순회하면 DOM 변경 영향 없음
선택 API 비교 정리
// 1. id로 단일 요소 — getElementById (가장 빠름)
document.getElementById('my-id');
// 2. CSS 선택자로 단일 요소 — querySelector
document.querySelector('.card:first-of-type');
// 3. CSS 선택자로 여러 요소 — querySelectorAll (정적)
document.querySelectorAll('input[required]');
// 4. 클래스로 여러 요소 — getElementsByClassName (실시간)
document.getElementsByClassName('active');
// 5. 태그로 여러 요소 — getElementsByTagName (실시간)
document.getElementsByTagName('button');
// 6. name 속성 — getElementsByName (form 요소)
document.getElementsByName('username');
matches / closest / contains
const li = document.querySelector('li.selected');
// 이 요소가 선택자와 일치하는지
li.matches('.selected'); // true
li.matches('li[data-id]'); // false
// 자신부터 위로 올라가며 선택자에 맞는 첫 요소
li.closest('ul'); // 상위 <ul>
li.closest('[data-list="main"]'); // data 속성 조상
li.closest('.없음'); // null
// 포함 여부
document.body.contains(li); // true
li.contains(document.body); // false
이벤트 위임에서 closest는 클릭된 자식 요소에서 원하는 조상을 찾는 표준 패턴입니다.
document.querySelector('.list').addEventListener('click', (e) => {
const item = e.target.closest('.list-item');
if (!item) return;
item.classList.toggle('selected');
});
복합 선택자 팁
// 여러 선택자 — OR 조건
document.querySelectorAll('h1, h2, h3');
// 속성 선택자
document.querySelectorAll('[data-lazy]');
document.querySelectorAll('img[src$=".webp"]');
// 형제 선택자
document.querySelectorAll('.active + .item'); // 바로 다음 형제
document.querySelectorAll('.active ~ .item'); // 이후 모든 형제
// 부정 선택자
document.querySelectorAll('li:not(.disabled)');
// has — 자식 조건부 선택 (CSS Selectors 4)
document.querySelectorAll('article:has(img)'); // 이미지를 포함한 article
:has()는 Chrome 105+, Safari 15.4+, Firefox 121+에서 지원됩니다.
실무 패턴: 안전한 단일 요소 선택
// null 체크 강제 패턴
function $(sel, ctx = document) {
const el = ctx.querySelector(sel);
if (!el) throw new Error(`Element not found: ${sel}`);
return el;
}
// 선택적 패턴 (없으면 null)
function $$(sel, ctx = document) {
return [...ctx.querySelectorAll(sel)];
}
// 사용
const header = $('#main-header'); // 없으면 throw
const buttons = $$('button[data-action]'); // 빈 배열 가능
지난 글: Document · Element · Node · Text — DOM의 핵심 인터페이스
다음 글: DOM 요소 생성·삽입·제거 — createElement부터 replaceWith까지
읽어주셔서 감사합니다. 😊