- Published on
2024.09.16
Edited 2024.09.22
[딥다이브] - 39. DOM(1)
DOM은 HTML 문서의 계층적 구조와 정보를 표현하며 이를 제어할 수 있는 API, 즉 프로퍼티와 메서드를 제공하는 트리 자료구조다.
39.1. 노드
39.1.1. HTML 요소와 노드 객체
HTML 요소는 렌더링 엔진에 의해 파싱되어 DOM을 구성하는 요소 노드 객체로 변환된다. HTML 요소의 어트리뷰트는 어트리뷰트 노드로, 텍스트 콘텐츠는 텍스트 노드로 변환된다.
HTML 요소 간에는 계층적인 부자 관계가 형성되며, HTML 요소를 객체화한 모든 노드 객체들을 트리 자료구조로 구성한다.
트리 자료구조
부모 노드와 자식 노드로 구성된 노드 간의 계층적 구조를 표현하는 비선형 자료구조
트리 자료구조는 부모 노드가 없는 하나의 루트 노드
(최상위 노드)에서 시작해 0개 이상의 자식 노드를 갖는다. 자식 노드가 없는 노드를 리프 노드
라 한다.
노드 객체들로 구성된 트리 자료구조를 DOM, DOM 트리라고 한다.
39.1.2. 노드 객체의 타입
DOM에는 12 종류의 노드 타입이 있고, 중요한 노드 타입은 다음 4가지다.
문서 노드
DOM 트리 최상위인 루트 노드로서 document
객체를 가리킨다. DOM 트리의 루트 노드이므로 요소, 어트리뷰트, 텍스트 노드에 접근하려면 문서 노드를 통해야 한다.
요소 노드
HTML 요소를 가리키는 객체로, 문서의 구조를 표현한다.
어트리뷰트 노드
HTML 요소의 어트리뷰트를 가리키는 객체로, 어트리뷰트 노드는 어트리뷰트가 지정된 HTML 요소의 요소 노드와 연결되어 있다. 단, 요소 노드는 부모 노드와 연결되어 있지만 어트리뷰트 노드는 부모 노드와는 연결되어 있지 않다. 그러므로 어트리뷰트 노드는 요소 노드의 형제 노드는 아니다. 따라서 어트리뷰트 노드에 접근하여 어트리뷰트를 참조하거나 변경하려면 요소 노드에 접근해야 한다.
텍스트 노드
HTML 요소의 텍스트를 가리키는 객체로, 문서의 정보를 표현한다. 요소 노드의 자식 노드이며, 자식 노드를 가질 수 없는 리프 노드다. 텍스트 노드에 접근하려면 먼저 부모 노드인 요소 노드에 접근해야 한다.
39.1.3. 노드 객체의 상속 구조
DOM을 구성하는 노드 객체는 DOM API를 사용해 노드를 탐색하거나 자신의 어트리뷰트와 텍스트를 조작할 수 있다.
노드 객체는 프로토타입 체인에 있는 모든 프로토타입의 프로퍼티와 메서드를 상속받을 수 있다. input 요소 노드 객체도 다음과 같이 다양한 특성을 갖는다.
input 요소 노드 객체의 특성 | 프로토타입을 제공하는 객체 |
---|---|
객체 | Object |
이벤트를 발생시키는 객체 | EventTarget |
트리 자료구조의 노드 객체 | Node |
브라우저가 렌더링할 수 있는 웹 문서의 요소를 표현하는 객체 | Element |
웹 문서의 요소 중에서 HTML 요소를 표현하는 객체 | HTMLElement |
HTML 요소 중에서 input 요소를 표현하는 객체 | HTMLInputElement |
노드 객체에는 노드 타입과 상관없이 모든 노드 객체가 공통으로 갖는 기능도 있고, 노드 타입에 따라 고유한 기능도 있다. 모든 노드 객체는 공통적으로 이벤트를 발생시킬 수 있고, 트리 자료구조의 노드로서 공통적으로 트리 탐색 기능이나 노드 정보 제공 기능이 필요하다. 이 같은 노드 관련 기능은 Node 인터페이스가 제공한다.
노드 객체는 공통된 기능일수록 프로토타입 체인 상위에, 고유 기능일수록 프로로타입 체인 하위에 프로토타입 체인을 구축해 상속 구조를 갖는다.
DOM은 HTML 문서의 계층적 구조와 정보 표현, 노드 타입에 따라 필요한 기능을 DOM API로 제공한다. 이 DOM API를 통해 HTML의 구조나 내용 또는 스타일 등을 동적으로 조작할 수 있다.
39.2. 요소 노드 취득
HTML의 구조나 내용, 스타일을 조작하려면 요소 노드를 취득해야 한다. 텍스트 노드는 요소 노드의 자식이고, 어트리뷰트 노드를 조작할 때도 마찬가지다.
39.2.1. id를 이용한 요소 노드 취득
Document.prototype.getElementById
는 인수로 전달한 id 어트리뷰트 값을 갖는 하나의 요소 노드를 탐색해 리턴한다. getElementById
는 Document.prototype
의 프로퍼티이므로 반드시 문서 노드인 Document
를 통해 호출해야 한다.
HTML 요소에 id 어트리뷰트를 부여하면 id 값과 동일한 이름의 전역 변수가 암묵적으로 선언되고 해당 노드 객체가 할당되는 부수 효과가 있다.
39.2.2. 태그 이름을 이용한 요소 노드 취득
Document.prototype/Element.prototype.getElementsByTagName
는 인수로 전달한 태그 이름을 갖는 모든 요소 노드들을 탐색해 리턴한다. 여러 개의 요소 노드 객체를 갖는 DOM 컬렉션 객체인 HTMLCollection
객체를 리턴한다. HTMLCollection
객체는 유사 배열 객체이면서 이터러블이다.
모든 요소 노드를 취득하려면 인수로 *
을 전달한다.
getElementsByTagName
은 Document.prototype
에 정의된 메서드와 Element.prototype
에 정의된 메서드가 있다. Document.prototype
은 document
를 호출하여 DOM 전체에서 요소 노드를 탐색하고, Element.prototype
은 특정 요소 노드를 통해 호출 후, 특정 요소 노드의 자손 노드 중에서 요소 노드를 탐색해 리턴한다.
39.2.3. class를 이용한 요소 노드 취득
Document.prototype/Element.prototype.getElementsByClassName
은 인수로 전달한 class 어트리뷰트 값을 갖는 모든 요소 노드들을 탐색하여 리턴한다. getElementsByTagName
과 마찬가지로 HTMLCollection
을 리턴하며, Document / Element
차이도 동일하다.
39.2.4. CSS 선택자를 이용한 요소 노드 취득
CSS 선택자는 스타일을 적용하고자 하는 HTML 요소를 특정할 때 사용한다.
Document.prototype/Element.prototype.querySelector
인수로 전달한 CSS 선택자를 만족시키는 하나의 요소 노드를 탐색하여 리턴한다.
- 인수로 전달한 CSS 선택자를 만족시키는 요소 노드가 여러 개인 경우 첫 번째 요소 노드만 리턴
- 인수로 전달한 CSS 선택자를 만족시키는 요소 노드가 존재하지 않는 경우
null
을 리턴 - 인수로 전달한 CSS 선택자가 문법에 맞지 않는 경우 DOMException 에러가 발생
Document.prototype/Element.prototype.querySelectorAll
인수로 전달한 CSS 선택자를 만족시키는 모든 요소 노드를 탐색하여 리턴한다.
- 인수로 전달된 CSS 선택자를 만족시키는 요소가 존재하지 않는 경우 빈 NodeList 객체를 리턴
- 인수로 전달한 CSS 선택자가 문법에 맞지 않는 경우 DOMException 에러가 발생
HTML 문서의 모든 요소 노도를 취득하려면 querySelectorAll
에 인수로 *
을 전달한다.
CSS 선택자 문법을 사용하는 querySelector
, querySelectorAll
은 getElementBy**
메서드보다 느린 것으로 알려져 있지만, 구체적인 조건과 일관된 방식으로 요소 노드를 취득할 수 있다는 장점이 있다. id 어트리뷰트가 있는 요소 노드를 취득하는 경우에만 getElementById
를 사용하고 그 외에는 querySelector
, querySelectorAll
을 사용하는 것을 권장한다.
39.2.5. 특정 요소 노드를 취득할 수 있는지 확인
Element.prototype.matches
는 인수로 전달한 CSS 선택자로 특정 요소 노드를 취득할 수 있는지 확인하고 Boolean
을 리턴한다.
39.2.6. HTMLCollection과 NodeList
HTMLCollection
노드 객체의 상태 변화를 실시간으로 반영하는 살아 있는 DOM 컬렉션 객체
실시간으로 노드 객체의 상태 변경을 반영하므로 순회하며 객체 상태를 변경할 때 부작용이 발생할 수 있다.
NodeList
실시간으로 노드 객체의 상태 변경을 반영하지 않는 DOM 컬렉션 객체NodeList
의 객체는 대부분 상태 변경을 실시간으로 반영하지 않는 non-live 상태를 동작해 HTMLCollection
의 부작용을 회피할 수 있지만, childNodes 프로퍼티가 리턴하는 NodeList 객체는 HTMLCollection과 같이 실시간으로 노드 객체의 상태 변경을 반영하는 live 객체로 동작하므로 주의가 필요하다.
노드 객체의 상태 변경과 상관없이 안전하게 DOM 컬렉션을 사용하려면 HTMLCollection이나 NodeList 객체를 배열로 변환하여 사용하는 것을 권장한다.
39.3. 노드 탐색
DOM 트리 상의 노드를 탐색할 수 있도록 Node
, Element
인터페이스는 트리 탐색 프로퍼티를 제공한다. 노드 탐색 프로퍼티는 모두 접근자 프로퍼티다. 단 setter없이 getter만 존재하는 읽기 전용 접근자 프로퍼티다.
39.3.1. 공백 텍스트 노드
스페이스, 탭, 줄바꿈(개행) 등의 공백 문자는 공백 텍스트 노드를 생성한다.
HTML 문서의 공백 문자는 공백 텍스트 노드를 생성한다. 노드를 탐색할 때 공백 문자가 생성한 공백 텍스트 노드에 주의해야 한다.
39.3.2. 자식 노드 탐색
프로퍼티 | 설명 |
---|---|
Node.prototype.childNodes | 자식 노드를 모두 탐색하여 NodeList에 담아 리턴한다. 요소 노드뿐만 아니라 텍스트 노드도 포함되어 있을 수 있다. |
Element.prototype.children | 자식 노드 중 요소 노드만 모두 탐색해 HTMLCollection에 담아 리턴한다. 텍스트 노드가 포함되지 않는다. |
Node.prototype.firstChild | 첫번째 자식 노드를 리턴한다. 텍스트 노드이거나 요소 노드다. |
Node.prototype.lastChild | 마지막 자식 노드를 리턴한다. 텍스트 노드이거나 요소 노드다. |
Element.prototype.firstElementChild | 첫번째 자식 노드를 리턴한다. 요소 노드만 리턴한다. |
Element.prototype.lastElementChild | 마지막 자식 노드를 리턴한다. 요소 노드만 리턴한다. |
<html>
<body>
<ul id="fruits">
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits')
// #fruits 요소의 모든 자식 노드를 탐색
// childNodes 프로퍼티가 반환한 NodeList에는 요소 노드뿐만 아니라 텍스트 노드도 포함되어 있다.
console.log($fruits.childNodes)
// NodeList(7) [text, li.apple, text, li.banana, text, li.orange, text]
// #fruits 요소의 모든 자식 노드를 탐색
// children 프로퍼티가 반환한 HTMLCollection에는 요소 노드만 포함되어 있다.
console.log($fruits.children)
// HTMLCollection(3) [li.apple, li.banana, li.orange]
// #fruits 요소의 첫 번째 자식 노드를 탐색
// firstChild 프로퍼티는 텍스트 노드를 반환할 수도 있다.
console.log($fruits.firstChild) // #text
// #fruits 요소의 마지막 자식 노드를 탐색
// lastChild 프로퍼티는 텍스트 노드를 반환할 수도 있다.
console.log($fruits.lastChild) // #text
// #fruits 요소의 첫 번째 자식 노드를 탐색
// firstElementChild 프로퍼티는 요소 노드만 반환한다.
console.log($fruits.firstElementChild) // li.apple
// #fruits 요소의 마지막 자식 노드를 탐색
// lastElementChild 프로퍼티는 요소 노드만 반환한다.
console.log($fruits.lastElementChild) // li.orange
</script>
</html>
39.3.3. 자식 노드 존재 확인
Node.prototype.hasChildNodes
는 자식 노드 존재 여부를 불리언으로 리턴한다. childNodes
프로퍼티와 마찬가지로 텍스트 노드를 포함하여 자식 노드의 존재를 확인한다. 텍스트 노드가 아닌 요소 노드가 존재하는지 확인하려면 children.length
또는 Element
인터페이스의 childElementCount
프로퍼티를 사용한다.
39.3.4. 요소 노드의 텍스트 노드 탐색
요소 노드의 텍스트 노드는 firstChild
프로퍼티로 접근할 수 있다. firstChild
프로퍼티가 리턴한 노드는 텍스트 노드이거나 요소 노드다.
39.3.5. 부모 노드 탐색
부모 노드를 탐색하려면 Node.prototype.parentNode
프로퍼티를 사용한다. 텍스트 노드는 DOM 트리의 리프 노드이므로 부모 노드가 텍스트 노드인 경우는 없다.
39.3.6. 형제 노드 탐색
어트리뷰트 노드는 요소 노드와 연결되어 있지만 부모 노드가 같은 형제 노드가 아니므로 리턴되지 않는다.
프로퍼티 | 설명 |
---|---|
Node.prototype.previousSibling | 자신의 이전 형제 노드를 리턴한다. 요소 노드뿐 아니라 텍스트 노드일 수도 있다. |
Node.prototype.nextSibling | 자신의 다음 형제 노드를 리턴한다. 요소 노드뿐 아니라 텍스트 노드일 수도 있다. |
Element.prototype.previousElementSibling | 자신의 이전 형제 요소 노드를 리턴한다. 요소 노드만 리턴한다. |
Element.prototype.nextElementSibling | 자신의 다음 형제 요소 노드를 리턴한다. 요소 노드만 리턴한다. |
39.4. 노드 정보 취득
프로퍼티 | 설명 |
---|---|
Node.prototype.nodeType | 노드 타입을 나타내는 상수를 리턴한다. |
Node.prototype.nodeName | 노드의 이름을 문자열로 리턴한다. |
노드 타입 상수
- Node.ELEMENT_NODE: 요소 노드 타입을 나타내는 상수 1
- Node.TEXT_NODE: 텍스트 노드 타입을 나타내는 상수 3
- Node.DOCUMENT_NODE: 문서 노드 타입을 나타내는 상수 9
노드의 이름
- 요소 노드: 대문자 문자열 (
UL
,LI
) - 텍스트 노드: 문자열
#text
- 문서 노드: 문자열
#document