1. 노드
HTML 요소와 노드 객체
- HTML 요소는 렌더링 엔진에 의해 파싱되어 DOM을 구성하는 요소 노드 객체로 변환
-> HTML 요소의 어트리뷰트는 어트리뷰트 노드로, HTML 요소의 텍스트 콘텐츠는 텍스트 노드로 변환
- HTML 요소 간에는 중첩 관계 (콘텐츠 영역에 다른 HTML 요소도 포함되는 것) 에 의해 계층적인 부자 관계가 형성됨 -> 이를 반영하여 HTML 문서의 구성 요소인 HTML 요소를 객체화한 모든 노드 객체들을 트리 자료구조로 구성한다.
- 트리 자료 구조 : 부모 노드와 자식 노드로 구성되어 노드 간의 계층적 구조(부자, 형제 관계)를 표현하는 비선형 자료구조
- DOM : 노드 객체들로 구성된 트리 자료구조 (DOM 트리라고 부르기도 함)
노드 객체의 타입
- 노드 객체는 총 12개의 종류가 있다.
문서 노드
- DOM 트리 최상위에 존재하는 루트 노드
- document 객체를 가리킴
- document 객체 : 브라우저가 렌더링한 HTML 문서 전체를 가리키는 객체로서 전역 객체 window의 document 프로퍼티에 바인딩되어 있음
- 문서 노드는 window.document 또는 document로 참조 가능
- HTML 문서당 document 객체는 유일
- document 객체는 DOM 트리의 루트 노드이므로 DOM 트리의 노드들에 접근하기 위한 진입점 역할을 담당
요소 노드
- HTML 요소를 가리키는 객체
- 문서의 구조를 표현
어트리뷰트 노드
- HTML 요소의 어트리뷰트를 가리키는 객체
- 어트리뷰트가 지정된 HTML 요소의 요소 노드와 연결되어 있음
- 어트리뷰트를 참조하거나 변경하려면 먼저 요소 노드에 접근해야 함
텍스트 노드
- HTML 요소의 텍스트를 가리키는 객체
- 문서의 정보를 표현
- 요소 노드의 자식 노드이며, 자식 노드를 가질 수 없는 리프 노드
노드 객체의 상속 구조
- DOM을 구성하는 노드 객체는 자신의 구조와 정보를 제어할 수 있는 DOM API를 사용할 수 있다.
=> 노드 객체는 자신의 부모, 형제, 자식을 탐색 가능, 자신의 어트리뷰트와 텍스트 조작 가능
- 위 그림처럼, 모든 노드 객체는 Object, EventTarget, Node 인터페이스를 상속받음
- 추가로, 문서 노드는 Document, HTMLDocument 인터페이스를 상속받고, 어트리뷰트 노드는 Attr, 텍스트 노드는 CharacterData 인터페이스를 각각 상속받음. 요소 노드는 Element 인터페이스와 HTMLElement, 종류 별로 세분화된 HTMLhtmlElement, HTMLHeadElement, HTMLBodyElement ... 등의 인터페이스를 상속받음
- ex) input 요소를 파싱하여 객체화한 input 요소 노드 객체는 HTMLInputElement, HTMLElement, Element, Node, EventTarget, Object의 prototype에 바인딩되어 있는 프로토 타입 객체를 상속받음
- input 요소 노드 객체도 아래와 같은 기능들을 상속을 통해 제공 받는다.
* 노드 객체의 상속 구조는 개발자 도구의 Elements 패널 우측의 Properties 패널에서 확인 가능하다.
- 모든 노드 객체가 공통적으로 갖는 기능 : EventTarget 인터페이스가 제공하는 이벤트 관련 기능 (EventTarget.addEventListener, EventTarget.removeEventListener 등), Node 인터페이스가 제공하는 트리 탐색 기능 (Node.parentNode, Node.childNodes, Node.previousSibiling, Node.nextSibiling 등), 노드 정보 제공 기능(Node.nodeType, Node.nodeName 등)
- 요소 노드 객체는 HTML 요소가 갖는 공통적인 기능이 존재
- input 요소 노드 객체와 div 요소 노드 객체는 모두 HTML 요소의 스타일을 나타내는 style 프로퍼티 존재
- HTML 요소의 종류에 따라 고유한 기능도 존재
- input 요소 노드 객체는 value 프로퍼티가 필요하지만, div 요소 노드 객체는 value 프로퍼티가 필요하지 않음
=> 필요한 기능을 제공하는 인터페이스 (HTMLInputElement, HTMLDivElement 등) 가 HTML 요소의 종류에 따라 각각 다름
- input 요소 노드 객체는 value 프로퍼티가 필요하지만, div 요소 노드 객체는 value 프로퍼티가 필요하지 않음
DOM은 HTML 문서의 계층적 구조와 정보를 표현하는 것은 물론 노드 객체의 종류, 즉 노드 타입에 따라 필요한 기능을 프로퍼티와 메서드의 집합인 DOM API(Application Programming Interface)로 제공한다. 이 DOM API를 통해 HTML의 구조나 내용 또는 스타일 등을 동적으로 조작할 수 있다.
2. 요소 노드 취득
HTML의 구조나 내용 또는 스타일 등을 동적으로 조작하려면 먼저 요소 노드를 취득해야 한다.
DOM은 요소 노드를 취득할 수 있는 다양한 메서드를 제공한다.
id를 이용한 요소 노드 취득
- Document.prototype.getElementById 메서드는 인수로 전달한 id 어트리뷰트 값을 갖는 하나의 요소 노드를 탐색하여 반환한다.
- getElementByld 메서드는 Document.prototype의 프로퍼티
- getElementByld 메서드는 언제나 단 하나의 요소 노드를 반환함
태그 이름을 이용한 요소 노드 취득
- Document.prototype / Element.prototype.getElementsByTagName 메서드는 인수로 전달한 태그 이름을 갖는 모든 요소 노드들을 탐색하여 반환한다.
- getElementsByTagName 메서드는 여러 개의 요소 노드 객체를 갖는 DOM 컬렉션 객체인 HTMLCollection 객체 (유사 배열 객체이자 이터러블) 를 반환한다.
- HTML 문서의 모든 요소 노드를 취득하려면 getElementsByTagName 메서드의 인수로 '*'를 전달한다.
// 모든 요소 노드를 탐색하여 반환한다.
const $all = document.getElementsByTagNameC'*');
// → HTMLCoiLection(8) [html, head, body, ul, li#appie, ii#banana, li#orange, script, apple:
li#apple, banana: li#banana, orange: li#orange]
- Document.prototype에 정의된 getElementsByTagName 메서드
- DOM의 루트 노드인 문서 노드, 즉 document를 통해 호출하며 DOM 전체에서 요소 노드를 탐색하여 반환한다.
- Element.prototype에 정의된 getElementsByTagName 메서드
- 특정 요소 노드를 통해 호출하며, 특정 요소 노드의 자손 노드 중에서 요소 노드를 탐색하여 반환한다.
<!D0CTYPE html>
<html>
<body>
<ul id="fruit">
<li>Apple</li>
<li>Banana</li>
<li>orange</li>
</u1>
<ul>
<li>HTML</ii>
</ul>
<script>
// DOM 전체에서 태그 이름이 li인 요소 노드를 모두 탐색하여 반환한다.
const $lisFromDocument = document.getElementsByTagName('li');
console.log($lisFromDocument); // HTMLCollection(4) [li, li, li, li]
// ul#fruits 요소의 자손 노드 중에서 태그 이름이 li인 요소 노드를 모두 탐색하여 반환한다.
const $fruits = document.getElementById('fruits');
const $lisFromFruits = $fruits.getElementsByTagName('li');
console.log($lisFromFruits); // HTMLCollection(3) [li, li, li]
</script>
</body>
</html>
class를 이용한 요소 노드 취득
- Document.prototype / Element. prototype.getElementsByClassName 메서드는 인수로 전달한 class 어트리뷰트 값(이하 class 값) 을 갖는 모든 요소 노드들을 탐색하여 반환한다.
- getElementsByClassName 메서드는 여러 개의 요소 노드 객체를 갖는 DOM 컬렉션 객체인 HTMLCollection 객체를 반환한다.
- Document.prototype에 정의된 getElementsByClassName 메서드
- DOM의 루트 노드인 문서 노드, 즉 document를 통해 호출하며 DOM 전체에서 요소 노드를 탐색하여 반환한다.
- Element.prototype에 정의된 getElementsByClassName 메서드
- 특정 요소 노드를 통해 호출하며, 특정 요소 노드의 자손 노드 중에서 요소 노드를 탐색하여 반환한다.
CSS 선택자를 이용한 요소 노드 취득
- css 선택자는 스타일을 적용하고자 하는 HTML 요소를 특정할 때 사용하는 문법이다.
/* 전체 선택자: 모든 요소를 선택 */
* { ... }
/* 태그 선택자: 모든 p 태그 요소를 모두 선택 */
p{ ... }
/* id 선택자: id 값이 'foo' 인 요소를 모두 선택 */
#foo{ ... }
/* class 선택자: class 값이 'foo'인 요소를 모두 선택 */
.foo{ ... }
/* 어트리뷰트 선택자: input 요소 중에 type 어트리뷰트 값이 'text’인 요소를 모두 선택 */
input[type=text] { ... }
/* 후손 선택자: div 요소의 후손 요소 중 p 요소를 모두 선택 */
div p{ ... }
/* 자식 선택자: div 요소의 자식 요소 중 p 요소를 모두 선택 */
div > p{ ... }
/* 인접 형제 선택자: p 요소의 형제 요소 중에 p 요소 바로 뒤에 위치하는 ul 요소를 선택 */
p + ul{ ... }
/* 일반 형제 선택자: p 요소의 형제 요소 중 p 요소 뒤에 위치하는 ul 요소를 모두 선택 */
p ~ ul{ ... }
/* 가상 클래스 선택자: hover 상태인 a 요소를 모두 선택 */
a:hover { ... }
/* 가상 요소 선택자: p 요소의 콘텐츠의 앞에 위치하는 공간을 선택
일반적으로 content 프로퍼티와 함께 시용된다. */
p::before { ... }
- Document.prototype / Element.prototype.querySelector 메서드는 인수로 전달한 css 선택자를 만족시키는 하나의 요소 노드를 탐색하여 반환한다.
- 인수로 전달한 CSS 선택자를 만족시키는 요소 노드가 여러 개인 경우 첫 번째 요소 노드만 반환한다.
- 인수로 전달된 CSS 선택자를 만족시키는 요소 노드가 존재하지 않는 경우 null을 반환한다.
- 인수로 전달한 CSS 선택자가 문법에 맞지 않는 경우 DOMException 에러가 발생한다.
- querySelectorAll 메서드는 여러 개의 요소 노드 객체를 갖는 DOM 컬렉션 객체인 NodeList 객체 (유사 배열 객체이면서 이터러블)를 반환한다
<!DOCTYPE html>
<html>
<body>
<ul>
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</ul>
<script>
// class 어트리뷰트 값이 'banana' 인 첫 번째 요소 노드를 탐색하여 반환한다.
const $elem = document.querySelector('.banana');
// 취득한 요소 노드의 style. color 프로퍼티 값을 변경한다.
$elem.style.color = 'red';
</script>
</body>
</html>
- Document.prototype에 정의된 querySelector, querySelectorAll 메서드
- DOM의 루트 노드인 문서 노드, 즉 document를 통해 호출하며 DOM 전체에서 요소 노드를 탐색하여 반환한다.
- Element.prototype에 정의된 querySelector, querySelectorAll 메서드
- 특정 요소 노드를 통해 호출하며, 특정 요소 노드의 자손 노드 중에서 요소 노드를 탐색하여 반환한다.
* id 어트리뷰트가 있는 요소 노드를 취득하는 경우에는 getElementByld 메서드를 사용하고 그 외의 경우에는 querySelector, querySelectorAll 메서드를 사용하는 것을 권장
특정 요소 노드를 취득할 수 있는지 확인
- Element.prototype.matches 메서드는 인수로 전달한 css 선택자를 통해 특정 요소 노드를 취득할 수 있는지 확인한다.
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</u1>
</body>
<script>
const $apple = document.querySetector('.apple');
// $apple 노드는 '#fruits > li.apple'로 취득할 수 있다.
console.log($apple.matches('#fruits> li.apple')); //true
// $apple 노드는 '#fruits > li.banana'로 취득할 수 없다.
console .log($apple.matches('#fruits > li.banana')); //false
</script>
</html>
HTMLCollection과 NodeList
DOM 컬렉션 객체인 HTMLCollection과 NodeList는 DOM API가 여러 개의 결과값을 반환하기 위한 DOM 컬렉션 객체다.
- 둘 다 유사 배열 객체이면서 이터러블 -> for ... of 문으로 순회 가능, 스프레드 문법을 사용하여 간단히 배열로 변환 가능
- 노드 객체의 상태 변화를 실시간으로 반영하는 살아 있는 객체
HTMLCollection
getElementsByTagName, getElementsByClassName 메서드가 반환하는 HTMLCollection 객체는 노드 객체의 상태 변화를 실시간으로 반영하는 살아 있는 DOM 컬렉션 객체다.
<!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>
* 3개 -> 0개를 예상했지만, 1개가 된 이유?
* 이 문제는 for 문을 역방향으로 순회하는 방법 / while문을 사용하는 방법으로 회피할 수 있다.
// for 문을 역방향으로 순회
for (let i = $elems.length - 1; i >= 0; i--) {
$elems[i].className = 'blue' ;
}
// while 문으로 HTMLCollection에 요소가 남아 있지 않을 때까지 무한 반복
let i = 0;
while ($elems.length > i) {
$elems[i].className = 'blue' ;
}
* 더 간단한 해결책 : HTMLCollection 객체를 배열로 변환하면 부작용을 발생시키는 HTMLCollection 객체를 사용할 필요가 없고. 유용한 배열의 고차 함수 (forEach, map, filter, reduce 등)를 사용할 수 있다.
// 유사 배열 객체이면서 이터러블인 HTMLCollection을 배열로 변환하여 순회
[... $elems].forEach(elem => elem.className = 'blue');
NodeList
querySelectorAll 메서드는 DOM 컬렉션 객체인 NodeList 객체를 반환한다. 이때 NodeList 객체는 실시간으로 노드 객체의 상태 변경을 반영하지 않는 non-live 객체다.
// querySelectorAll은 DOM 컬렉션 객체인 NodeList를 반환한다.
const $elems = document.querySelectorAll('.red');
// NodeList 객체는 "NodeList.prototype.forEach 메서드를 상속받아 사용할 수 있다.
$elems.forEach(elem => elem.className = 'blue');
- querySelectorAll이 반환하는 NodeList 객체는 NodeList.prototype.forEach 메서드를 상속받아 사용할 수 있다.
- NodeList.prototype은 forEach 외에도 item, entries, keys, values 메서드를 제공한다.
- NodeList 객체는 대부분의 경우 노드 객쳬의 상태 변경을 실시간으로 반영하지 않고 과거의 정적 상태를 유지하는 non-live 객쳬로 동작한다. 하지만 childNode 프로퍼티가 반환하는 NodeList 객체는 HTMLCollection 객체와 같이 실시간으로 노드 객체의 상태 변경을 반영하는 live 객체로 동작하므로 주의가 필요하다.
* live 객체로 동작할 때는 예상과 다르게 동작할 수 있어 실수하기 쉽다. 따라서 노드 객체의 상태 변경과 상관없이 안전하게 DOM 컬렉션을 사용하려면 HTMLCollection이나 NodeList 객체를 배열로 변환하여 사용하는 것을 권장한다.
- HTMLCollection과 NodeList 객체는 모두 유사 배열 객쳬이면서 이터러블이므로 스프레드 문법이나 Array.from 메서드를 사용하여 간단히 배열로 변환할 수 있다.
<!DOCTYPE html>
<html>
<body>
<u1 id="fruits">
<li>Apple</li>
<li>Banana</li>
</ul>
</body>
<script>
const $fruits = document.getElementByld('fruits');
// childNodes 프로퍼티는 NodeList 객체(live)를 반환한다.
const { childNodes } = $fruits;
// 스프레드 문법을 시용하여 NodeList 객체를 배열로 변환한다.
[... childNodes].forEach(childNode => {
$fruits.removeChild(childNode);
});
// $fruits 요소의 모든 자식 노드가 모두 삭제되었다.
console.log(childNodes); // NodeList []
</script>
</html>
3. 노드 탐색
취득한 요소 노드를 기점으로 DOM 트리의 노드를 옮겨 다니며 부모, 형제, 자식 노드 등을 탐색하는 방법을 살펴보자!
- Node, Element 인터페이스는 트리 탐색 프로퍼티를 제공한다.
- Node.prototype이 제공하는 프로퍼티 : parentNode, previousSibling, firstChild, childNodes 프로퍼티
- Element.prototype이 제공하는 프로퍼티 : previousElementSibling, nextElenientSibling과 children 프로퍼티
- 노드 탐색 프로퍼티는 모두 접근자 프로퍼티이다.
- 노드 탐색 프로퍼티는 setter없이 getter만 존재하여 참조만 가능한 읽기 전용 접근자 프로퍼티이다.
공백 텍스트 노드
텍스트 에디터에서 HTML 문서에 스페이스 키, 탭 키, 엔터 키 등을 입력하면 공백 문자가 추가된다.
<!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>
'스터디 > [모던 자바스크립트 deep dive] JS' 카테고리의 다른 글
#38. 브라우저의 렌더링 과정 (1) | 2024.09.08 |
---|---|
#37. Set과 Map (0) | 2024.09.07 |
#36. 디스트럭처링 할당 (구조 분해 할당) (0) | 2024.09.07 |
#35. 스프레드 문법 (0) | 2024.09.07 |
#27. 배열 (0) | 2024.08.18 |