DOM 요소를 선택할 때 JavaScript 개발자들은 다양한 메서드를 쓴다. 그 중에서 querySelector()와 getElementById()는 가장 흔히 사용되는데, 대부분의 개발자들은 둘의 정확한 차이와 성능 차이를 모르고 그냥 편한 대로 섞어 쓴다. 이번에는 두 메서드가 정확히 뭔지, 언제 뭘 써야 하는지, 그리고 어떤 게 더 빠른지 완벽하게 정리해서 소개하겠다.

 

1단계. querySelector()와 getElementById() 기초 이해하기

querySelector()는 CSS 선택자를 사용해서 DOM 요소를 찾는 범용 메서드다. 클래스, ID, 속성, 자식 요소 등 CSS에서 쓸 수 있는 모든 선택자를 지원한다. 반면 getElementById()는 오직 ID만으로 요소를 찾는다. 이게 핵심 차이다.

// querySelector 사용 예
const elem1 = document.querySelector('#myId');
const elem2 = document.querySelector('.myClass');
const elem3 = document.querySelector('[data-value="123"]');
const elem4 = document.querySelector('div > p:first-child');

// getElementById 사용 예
const elem5 = document.getElementById('myId');

querySelector()는 CSS 선택자 문법이 필요하지만, 매우 유연하다. 한 번에 복잡한 조건으로 요소를 찾을 수 있다. 반면 getElementById()는 단순하고 직관적이다. 하지만 ID로만 찾을 수 있다.

 

2단계. 반환 값과 에러 처리 차이

querySelector()와 getElementById() 모두 요소를 찾지 못하면 null을 반환한다. 이 점은 같다. 하지만 querySelector()는 querySelector 계열 메서드 중에서 querySelectorAll()도 있어서, 복수 선택이 필요할 때 바로 전환할 수 있다. getElementById()는 복수 선택이 불가능하다.

메서드 반환 타입 요소 없을 때 복수 선택
querySelector() Element 또는 null null 반환 querySelectorAll()로 가능
getElementById() HTMLElement 또는 null null 반환 불가능

 

3단계. 실전 코드 비교

다음은 같은 작업을 두 메서드로 하는 예제다.

// ✗ 잘못된 방법: querySelector로 ID 찾을 때 # 빼먹기
const elem = document.querySelector('myId');
// 이건 'myId'라는 태그를 찾으려고 한다(존재하지 않으므로 null)

// ✓ 올바른 방법: querySelector로 ID 찾으려면 # 필수
const elem = document.querySelector('#myId');

// ✓ 또 다른 올바른 방법: getElementById는 # 없음
const elem = document.getElementById('myId');

이 차이는 매우 중요하다. querySelector는 CSS 문법을 따르므로 ID 앞에 #을 붙여야 한다. 빼먹으면 태그명으로 인식된다.

// 클래스로 첫 번째 요소 찾기
// ✗ querySelectorAll로 무조건 다 찾으면 성능 낭비
const elems = document.querySelectorAll('.myClass');
const firstElem = elems[0];

// ✓ querySelector로 첫 번째만 찾기
const firstElem = document.querySelector('.myClass');

 

4단계. 성능 비교

성능은 querySelector()보다 getElementById()가 더 빠르다. getElementById()는 브라우저가 특별 최적화를 제공하는 메서드기 때문이다. 수백만 개 요소가 있는 거대한 페이지라면 차이가 눈에 띄지만, 일반적인 웹페이지에서는 체감 차이가 거의 없다.

// ID로 요소 찾기: 성능 비교

// 방법 1: querySelector (약간 느림)
const start1 = performance.now();
for (let i = 0; i < 1000000; i++) {
  document.querySelector('#myId');
}
const end1 = performance.now();
console.log('querySelector:', end1 - start1, 'ms');

// 방법 2: getElementById (더 빠름)
const start2 = performance.now();
for (let i = 0; i < 1000000; i++) {
  document.getElementById('myId');
}
const end2 = performance.now();
console.log('getElementById:', end2 - start2, 'ms');
// 결과: querySelector는 보통 150-200ms, getElementById는 50-80ms 정도

실무에서는 요소를 반복해서 선택하는 게 아니라 한 두 번만 선택하므로, 이 성능 차이는 무시할 수 있다. 중요한 건 선택자의 복잡도다. CSS 선택자가 복잡할수록 querySelector()는 느려진다.

// ✗ 복잡한 선택자: querySelector로는 느림
const elem = document.querySelector('div.container > ul > li:not(.disabled) > a[href*="api"]');

// ✓ 더 효율적: 간단한 선택자로 먼저 찾고, 필터링
const container = document.querySelector('.container');
const links = container.querySelectorAll('a');
const apiLinks = Array.from(links).filter(link => 
  link.href.includes('api') && !link.parentElement.classList.contains('disabled')
);

 

5단계. 주의사항과 실무 팁

querySelector()는 CSS 선택자를 문자열로 받으므로, 특수문자가 ID에 포함되면 이스케이프 처리가 필요하다. 반면 getElementById()는 문자열이 그냥 ID 값이므로 복잡할 일이 없다.

// ✗ 잘못된 방법: ID에 점이나 콜론이 있을 때
const elem = document.querySelector('#my.id');
// 이건 클래스 'id'를 가진 'my' ID 요소를 찾으려고 함(틀림)

// ✓ 올바른 방법: 이스케이프 처리 필수
const elem = document.querySelector('#my\\.id');

// ✓ 더 간단: getElementById 사용
const elem = document.getElementById('my.id');

또 다른 팁은 특정 요소 안에서만 찾을 때다. querySelector는 document뿐만 아니라 요소 객체에서도 쓸 수 있다. 이건 검색 범위를 좁혀서 성능을 올린다.

// 전체 문서에서 찾기
const elem1 = document.querySelector('.myClass');

// 특정 컨테이너 안에서만 찾기 (성능 더 좋음)
const container = document.getElementById('container');
const elem2 = container.querySelector('.myClass');

// getElementById는 document에서만 가능
const elem3 = container.getElementById('myId'); // 에러 없지만 작동 안 함
const elem3 = document.getElementById('myId');   // 올바른 방법

 

6단계. 실무 의사결정 가이드

ID로 단순히 요소를 찾을 때만 getElementById()를 쓰자. 성능이 조금 더 빠르고 의도가 명확하다. 클래스, 속성, 복합 조건으로 찾아야 할 때는 querySelector()를 쓴다. 여러 요소를 찾아야 한다면 querySelectorAll()을 쓴다. 이렇게 도구를 구분해서 쓰면, 코드의 의도가 명확해진다.

// 패턴 1: ID로 찾기
const header = document.getElementById('header');

// 패턴 2: 클래스로 첫 번째만 찾기
const button = document.querySelector('.primary-button');

// 패턴 3: 속성으로 찾기
const apiEndpoint = document.querySelector('[data-api="users"]');

// 패턴 4: 여러 개 찾기
const allItems = document.querySelectorAll('.list-item');

// 패턴 5: 컨테이너 안에서만 찾기
const modal = document.getElementById('userModal');
const closeButton = modal.querySelector('.close-btn');

DOM 선택은 JavaScript 개발의 기본이다. 작은 선택이 모여서 복잡한 인터랙션을 만드는데, 선택자를 올바르게 이해하고 상황에 맞게 쓰면 코드의 가독성과 성능을 동시에 높일 수 있다. 이 글의 패턴을 참고해서 지금 당신이 짜고 있는 코드의 DOM 선택 부분을 검토해보자. 불필요하게 querySelector를 남발하거나, 반대로 querySelector를 써야 할 자리에서 getElementById만 고집하는 패턴이 있다면 지금이 개선할 기회다.