본문으로 건너뛰기

요소 찾기: querySelector

자바스크립트 목차웹 페이지 움직이기레슨 66 / 76

목차

브라우저가 만든 요소 나무(DOM)에서 원하는 요소를 자바스크립트로 집어낼 때는 **document.querySelector(선택자)**를 부릅니다. 일치하는 첫 번째 요소 객체를 돌려주며, 화면에 그 선택자와 맞는 요소가 없으면 오류를 던지는 대신 **null**을 돌려줍니다.

5단계 중 1단계. document.querySelector('#count')는 요소 나무에서 id="count"인 첫 요소 객체를 찾아 돌려줍니다.

1하나 찾기: document.querySelector

할 일 앱의 index.html에 있는 개수 문단(<p id="count">)과 첫 번째 할 일 줄(<li>)을 찾아봅니다.

<p id="count" class="count">
  전체 3개 중 2개 남음
</p>
<ul id="todo-list">
  <li class="done">복습하기</li>
  <li>산책하기</li>
</ul>
결과 화면

document.querySelector⁠에 앞 레슨에서 본 선택자 문자열('#count', '#todo-list li')을 넘기면, 요소 나무를 위에서 아래로 훑어 처음으로 일치하는 요소 하나를 돌려줍니다.

// app.js
'use strict';

const countEl = document
  .querySelector('#count');
const firstLi = document
  .querySelector(
    '#todo-list li',
  );
console.log(countEl.tagName);
console.log(
  firstLi.textContent,
);
실행 결과
P
복습하기
  • '#count'는 id="count"인 <p> 요소 객체를 찾아 countEl에 붙입니다.
  • '#todo-list li'는 #todo-list 안의 <li> 두 줄 가운데 첫 번째 줄만 돌려줍니다.

2못 찾으면 null — 오류는 null을 쓰는 줄에서 난다

선택자 철자가 틀렸거나 #을 빼먹어서 일치하는 요소가 하나도 없으면 어떻게 될까요? document.querySelector⁠는 그 줄에서 오류를 내지 않고 조용히 null을 돌려줍니다.

'use strict';

const wrong = document
  .querySelector('count');
console.log(wrong);
실행 결과
null

'count'는 <count>라는 태그를 찾으라는 뜻이라 id="count"인 문단을 찾지 못하고 wrong에 null이 붙습니다. 진짜 오류는 이 wrong에 점(.)을 찍어 속성을 읽거나 고치는 아랫줄에서 터집니다.

wrong에 null이 들어 있을 때 wrong.textContent = '0개'⁠를 실행하는 상황을 Node에서 그대로 재현해 봅니다.

const wrong = null;
wrong.textContent = '0개';
실행 결과
main.js:2wrong.textContent = '0개';                  ^ TypeError: Cannot set properties of null (setting 'textContent')    at main.js:2:19

오류 메시지의 Cannot set properties of null⁠은 **"null에는 'textContent' 속성을 넣을 수 없다"**는 뜻입니다. 오류 번호는 2번 줄을 가리키지만, 진짜 원인은 2번 줄이 아니라 위쪽 querySelector에서 요소를 못 찾아 null을 받은 것입니다.

3여러 개 모두 찾기: document.querySelectorAll

조건에 맞는 요소를 첫 번째 하나만이 아니라 모두 찾고 싶을 때는 document.querySelectorAll(선택자)⁠를 씁니다.

'use strict';

const items = document
  .querySelectorAll(
    '#todo-list li',
  );
console.log(items.length);

for (const li of items) {
  console.log(li.textContent);
}
실행 결과
2
복습하기
산책하기

querySelectorAll이 돌려주는 묶음(NodeList)은 .length와 for...of 반복문은 바로 쓸 수 있지만, 진짜 배열이 아니라서 .map()이나 .filter()는 바로 부를 수 없습니다. 배열 메서드가 필요할 때는 펼침 연산자로 **[...items]**처럼 감싸 진짜 배열로 복사해서 씁니다.

예제

결과를 먼저 생각한 뒤 펼쳐 보세요. 맞혀 보기는 점수에 들지 않습니다.

다음 HTML에서 app.js를 돌렸을 때 콘솔에 찍히는 두 줄을 맞춰 보세요.

<div id="filters">
  <button data-filter="all">
    전체
  </button>
  <button data-filter="done">
    완료
  </button>
</div>
결과 화면
'use strict';

const a = document
  .querySelector(
    '[data-filter="done"]',
  );
const b = document
  .querySelector('filters');
console.log(
  a.textContent.trim(),
);
console.log(b);
결과 보기
실행 결과
완료
null
  • a는 data-filter="done"인 두 번째 단추를 찾아 그 안의 글자 '완료'를 찍습니다.
  • b는 '#filters'에서 #을 빼먹어 <filters> 태그를 찾으려 했으므로 요소를 찾지 못하고 null이 찍힙니다.

자주 틀리는 곳

  1. Cannot set properties of null⁠이 난 줄만 쳐다본다: 이 TypeError는 el.textContent = ... 줄에서 나지만, 그 줄 자체는 잘못이 없습니다. el을 만든 위쪽 document.querySelector(...)⁠의 선택자 오타를 고쳐야 합니다.
  2. id를 찾으면서 #을 빼먹는다: querySelector('todo-form')⁠은 <todo-form> 태그를 찾습니다. id="todo-form"을 찾으려면 반드시 앞에 #을 붙여 '#todo-form'으로 적어야 합니다.
  3. defer 없이 스크립트를 먼저 돌려 null이 나온다: 선택자 철자가 맞는데도 null이 나온다면, HTML 요소가 만들어지기 전에 스크립트가 먼저 실행된 것입니다. <script src="app.js" defer>⁠에 defer가 붙어 있는지 확인합니다.
  4. querySelectorAll 결과에 바로 .filter()나 .map()을 부른다: NodeList에는 배열의 .filter나 .map이 없어 TypeError가 납니다. [...items]처럼 배열로 펼친 뒤에 부릅니다.

확인 문제

  1. 화면에 class="delete"인 삭제 단추가 세 개 있습니다. querySelector('.delete')⁠와 querySelectorAll('.delete')⁠는 각각 무엇을 돌려주나요?
정답

querySelector('.delete')⁠는 첫 번째 삭제 단추 요소 하나만 돌려주고, querySelectorAll('.delete')⁠는 세 삭제 단추가 모두 담긴 목록(NodeList, 길이 3)을 돌려줍니다.

  1. 다음 코드를 브라우저에서 실행했더니 5번 줄에서 Cannot read properties of null⁠이라는 TypeError가 났습니다. 몇 번 줄을 어떻게 고쳐야 id="quote"인 요소를 찾을 수 있나요?
'use strict';
const q = document
  .querySelector('quote');
console.log(q.textContent);
정답

3번 줄의 'quote'를 '#quote'로 고칩니다. 오류는 4번 줄에서 났지만 원인은 2~3번 줄에서 #을 빼먹어 q가 null이 된 것입니다.

  1. querySelectorAll로 '#filters button' 단추 목록 btns를 찾았습니다. .map()을 써서 각 단추의 글자 배열을 뽑으려면 btns를 어떻게 바꿔야 하나요?
정답

[...btns].map(...)처럼 펼침 연산자 [...btns]로 감싸 배열로 만듭니다.

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

자바스크립트 목차로