브라우저가 만든 요소 나무(DOM)에서 원하는 요소를 자바스크립트로 집어낼 때는 **document.querySelector(선택자)**를 부릅니다. 일치하는 첫 번째 요소 객체를 돌려주며, 화면에 그 선택자와 맞는 요소가 없으면 오류를 던지는 대신 **null**을 돌려줍니다.
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);
결과 보기
완료
nulla는data-filter="done"인 두 번째 단추를 찾아 그 안의 글자'완료'를 찍습니다.b는'#filters'에서#을 빼먹어<filters>태그를 찾으려 했으므로 요소를 찾지 못하고null이 찍힙니다.
자주 틀리는 곳
Cannot set properties of null이 난 줄만 쳐다본다: 이TypeError는el.textContent = ...줄에서 나지만, 그 줄 자체는 잘못이 없습니다.el을 만든 위쪽document.querySelector(...)의 선택자 오타를 고쳐야 합니다.id를 찾으면서#을 빼먹는다:querySelector('todo-form')은<todo-form>태그를 찾습니다.id="todo-form"을 찾으려면 반드시 앞에#을 붙여'#todo-form'으로 적어야 합니다.defer없이 스크립트를 먼저 돌려null이 나온다: 선택자 철자가 맞는데도null이 나온다면, HTML 요소가 만들어지기 전에 스크립트가 먼저 실행된 것입니다.<script src="app.js" defer>에defer가 붙어 있는지 확인합니다.querySelectorAll결과에 바로.filter()나.map()을 부른다:NodeList에는 배열의.filter나.map이 없어TypeError가 납니다.[...items]처럼 배열로 펼친 뒤에 부릅니다.
확인 문제
- 화면에
class="delete"인 삭제 단추가 세 개 있습니다.querySelector('.delete')와querySelectorAll('.delete')는 각각 무엇을 돌려주나요?
정답
querySelector('.delete')는 첫 번째 삭제 단추 요소 하나만 돌려주고, querySelectorAll('.delete')는 세 삭제 단추가 모두 담긴 목록(NodeList, 길이 3)을 돌려줍니다.
- 다음 코드를 브라우저에서 실행했더니 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이 된 것입니다.
querySelectorAll로'#filters button'단추 목록btns를 찾았습니다..map()을 써서 각 단추의 글자 배열을 뽑으려면btns를 어떻게 바꿔야 하나요?
정답
[...btns].map(...)처럼 펼침 연산자 [...btns]로 감싸 배열로 만듭니다.