일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- memory
- Java
- BOJ
- DoM
- EventListener
- Dive
- API
- 프로젝트
- LeetCode
- axios
- 요청
- deep
- 프로그래머스
- 상태
- 개발
- 꿀팁
- programmers
- 백준
- Hook
- virtual Dom
- 유용한 사이트
- javascript
- 원리
- 상태 끌어올리기
- 가상 DOM
- react
- State
- Today
- Total
탄탄한 기본기!
DOM(Document Object Model) - HTMLCollection과 NodeList 본문
DOM(Document Object Model)은 HTML 문서의 계층적 구조와 정보를 표현하며 이를 제어할 수 있는 API, 즉 프로퍼티와 메서드를 제공하는 트리 자료구조이다.
1. 노드
<div class='hello'>Hello</div>
위와 같은 HTML요소는 div 요소 노드를 생성하는데, 이때 어트리뷰트는(class, id 등) 어트리뷰트 노드로, 텍스트 콘텐츠의 경우에는 텍스트 노드로 따로 구분된다. 참고로 HTML의 태그들 사이의 개행이나 공백들은 텍스트 노드가 된다.
HTML 문서는 이러한 요소들의 집합으로 이루어지며, 중첩 관계를 통해 트리 구조로 구성된다.
1.1 노드의 종류
노드의 타입은 문서 노드, 요소 노드, 어트리뷰트 노드, 텍스트 노드로 나눌 수 있다.
문서 노드는 DOM 트리의 최상위에 존재하는 루트 노드로서 document 객체를 가리킨다. 이는 HTML 문서 전체를 가리키는 객체로서 전역 객체 window의 document 프로퍼티에 바인딩되어 있다.
요소 노드는 HTML 요소를 가리키는 객체이며 부자 관계를 이루는 중첩 관계를 형성하고 있다.
어트리뷰트 노드는 HTML 요소의 어트리뷰트를 가리키는 객체이며 부모 노드와 연결되어 있는 것이 아니라 요소 노드에만 연결되어 있다.
텍스트 노드는 HTML 요소의 텍스트를 가리키는 객체이다. 텍스트 노드는 자식을 가질 수 없는 리프 노드이다.
1.2 노드 취득
노드를 취득하는 방법에는 가장 첫 번째 요소를 반환하는 Document.prototype.getElementById메서드를 이용하거나,
HTMLCollection을 반환하는 Document.prototype.getElementsByTagName(document를 통해 호출하면 전체 요소 노드 중 태그 이름에 해당하는 요소들을, 요소를 통해서 호출하면 해당 요소 노드의 자손 노드들 중에서 태그 이름에 해당하는 요소들을 반환하며 찾을 수 없는 경우에 빈 HTMLCollection 객체를 반환함),
Document.prototype.getElementsByTagName과 비슷하게 동작하지만 class로 요소들을 취득하는 getElementsByClassName메서드 등이 있다.
그리고 이러한 객체 말고, CSS 선택자를 활용해서 요소들을 취득하는 Document.prototype.querySelector/querySelectorAll이 존재한다. 이 중 querySelectorAll메서드는 HTMLCollection이 아니라 NodeList객체를 반환한다. 그리고 NodeList객체는 유사 배열 객체이며, 이터러블이다.
<!DOCTYPE html>
<html>
<body>
<ul>
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</ul>
<script>
// ul 요소의 자식 요소인 li 요소를 모두 탐색하여 반환한다.
const $elems = document.querySelectorAll('ul > li');
// 취득한 요소 노드들은 NodeList 객체에 담겨 반환된다.
console.log($elems); // NodeList(3) [li.apple, li.banana, li.orange]
// 취득한 모든 요소 노드의 style.color 프로퍼티 값을 변경한다.
// NodeList는 forEach 메서드를 제공한다.
$elems.forEach(elem => { elem.style.color = 'red'; });
</script>
</body>
</html>
2. HTMLCollection과 NodeList
HTMLCollection과 NodeList는 모두 유사 배열 객체이면서 이터러블이다. 따라서 for…of 문으로 순회할 수 있으며 스프레드 문법을 사용하여 간단히 배열로 변환할 수 있다.
중요한 사실은, HTMLCollection과 NodeList은 노드 객체의 상태 변화를 실시간으로 반영하는 살아 있는(live) 객체라는 점이다. HTMLCollection은 항상 live 객체로 동작한다. 하지만 NodeList은 대부분 실시간으로 동작하지 않고 과거 정적인 상태를 유지하는 non-live객체로 동작하며 경우에 따라서 live객체로 동작한다.
<!DOCTYPE html>
<head>
<style>
.red { color: red; }
.blue { color: blue; }
</style>
</head>
<html>
<body>
<ul id="fruits">
<li class="red">Apple</li>
<li class="red">Banana</li>
<li class="red">Orange</li>
</ul>
<script>
// class 값이 'red'인 요소 노드를 모두 탐색하여 HTMLCollection 객체에 담아 반환한다.
const $elems = document.getElementsByClassName('red');
// 이 시점에 HTMLCollection 객체에는 3개의 요소 노드가 담겨 있다.
console.log($elems); // HTMLCollection(3) [li.red, li.red, li.red]
// HTMLCollection 객체의 모든 요소의 class 값을 'blue'로 변경한다.
for (let i = 0; i < $elems.length; i++) {
$elems[i].className = 'blue';
}
// HTMLCollection 객체의 요소가 3개에서 1개로 변경되었다.
console.log($elems); // HTMLCollection(1) [li.red]
</script>
</body>
</html>
위 예제는 클래스 이름이 red인 요소들을 취득한 뒤, 반복문을 통해서 클래스를 blue로 바꾸는 간단한 예제이다. 하지만 이는 우리의 의도대로 동작하지 않는다.
첫 번째 반복 때 첫 번째 li 요소는 class 값이 red에서 blue로 변경되었으므로 getElementsByClassName 메서드의 인자로 전달한 red와 더는 일치하지 않기 때문에 $elems에서 실시간으로 제거된다.
따라서 우리가 원하는 것과 전혀 다르게 동작하며, 배열과 같이 정적으로 동작하지 않는다는 사실을 알 수 있다. 이 문제는 역방향으로 순회하여서 회피할 수도 있지만 그다지 좋은 선택은 아닌 것으로 보이며 배열로 변환한 후 고차 함수(map, filter 등)를 사용하는 것이 좋다.
참고로 querySelectorAll이 반환하는 NodeList는 대부분 정적으로 동작하며 Node.prototype이 제공하는 forEach메서드를 사용할 수 있다. 하지만, childNodes프로퍼티가 반환하는 NodeList는 HTMLCollection과 같이 live객체로써 동작하기 때문에 주의해야 한다.
이처럼 HTMLCollection이나 NodeList 객체는 예상과 다르게 동작할 때가 있어 다루기 까다롭고 실수하기 쉽다. 따라서 노드 객체의 상태 변경과 상관없이 안전하게 DOM 컬렉션을 사용하려면 HTMLCollection이나 NodeList 객체를 배열로 변환하여 사용하는 것을 권장한다.
3. 노드 탐색
DOM 트리 상의 노드를 탐색할 수 있도록 Node, Element 인터페이스는 트리 탐색 프로퍼티를 제공한다.
Node.prototype이 제공하는 parentNode, previousSibling, nextSibling, firstChild, lastChild, childNodes 프로퍼티, 그리고 Element.prototype이 제공하는 previousElementSibling, nextElementSibling, children 프로퍼티가 존재한다.
노드 탐색 프로퍼티는 모두 접근자 프로퍼티다. 단, 노드 탐색 프로퍼티는 setter 없이 getter만 존재하여 참조만 가능한 읽기 전용 접근자 프로퍼티다.
3.1 공백 텍스트 노드
HTML 요소 사이의 스페이스, 탭, 줄 바꿈(개행) 등의 공백(white space) 문자는 텍스트 노드를 생성하며, 이를 공백 텍스트 노드라 한다.
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</ul>
</body>
</html>
따라서 ul태그 전, li태그들 사이에 개행들이 모두 공백 노드로 생성되어 DOM을 함께 구성하고 있는 것이다. 공백 노드는 노드를 탐색할 때에도 영향을 끼칠 수 있기 때문에 잘 인지하고 있어야 한다.
예를 들어서, Node.prototype이 제공하는 childNodes의 경우에는 반환한 NodeList를 반환하는데, 여기에는 텍스트 노드도 포함되기 때문에 공백 텍스트도 포함될 수 있다. 하지만 Element.prototype이 제공하는 children의 경우에는 HTMLCollection에 담아서 반환하는데 여기에는 텍스트 노드가 포함되지 않고 요소 노드만 포함된다.
'개인 공부 > JS (자바스크립트)' 카테고리의 다른 글
javaScript - Ajax (0) | 2021.06.27 |
---|---|
javaScript - 디바운스(debounce), 스로틀(throttle) (0) | 2021.06.10 |
javaScript - Set과 Map (0) | 2021.06.03 |
javaScript - 이터러블과 이터레이터 (0) | 2021.06.01 |
javaScript - Symbol 타입 (0) | 2021.06.01 |