DOM(Document Object Model) 개요
DOM은 HTML 문서의 계층적 구조와 정보를 표현하고 이를 제어할 수 있는 API를 제공하는 트리 자료구조다.
브라우저의 렌더링 엔진은 HTML 문서를 파싱하여 브라우저가 이해할 수 있는 자료구조인 DOM을 생성하는데, 이를 통해 자바스크립트가 HTML 요소들을 동적으로 조작할 수 있게 된다.
DOM은 HTML 문서의 내용과 구조가 객체 모델로 변환되어 다양한 프로그램에서 사용될 수 있게 만든 인터페이스라고 할 수 있다.
노드
HTML 요소와 노드 객체
HTML 요소의 구조와 변환
HTML 요소는 HTML 문서를 구성하는 개별적인 요소로, 시작 태그와 종료 태그, 어트리뷰트, 콘텐츠로 구성된다. 시작 태그는 요소의 시작을 나타내고, 종료 태그는 요소의 끝을 나타낸다. 어트리뷰트는 요소의 부가적인 정보를 담고 있으며, 콘텐츠는 요소의 실제 내용을 포함한다.
렌더링 엔진은 이러한 HTML 요소를 파싱하여 DOM을 구성하는 요소 노드 객체로 변환한다. 이 과정에서 HTML 요소의 어트리뷰트는 어트리뷰트 노드로, HTML 요소의 텍스트 콘텐츠는 텍스트 노드로 각각 변환된다. 이렇게 변환된 노드들은 서로 계층적인 관계를 형성하게 된다.
트리 자료구조의 특성
HTML 요소들은 서로 중첩될 수 있으며, 이러한 중첩 관계는 부모-자식 관계로 표현된다. DOM은 이러한 HTML 요소들의 계층적 관계를 트리 자료구조로 구현한다. 트리 자료구조는 노드들의 계층적 구조를 표현하는 비선형 자료구조로, 각 노드는 부모 노드와 자식 노드들로 구성된다.
트리의 최상위에는 루트 노드가 위치하는데, 이 노드는 부모 노드를 가지지 않으며 0개 이상의 자식 노드를 가질 수 있다. 반면 리프 노드는 자식 노드가 없는 말단 노드를 의미한다. 이러한 구조를 통해 DOM은 문서의 구조를 효율적으로 표현하고 조작할 수 있게 된다.
노드 객체의 타입
문서 노드(Document Node)의 특징
문서 노드는 DOM 트리의 최상위에 존재하는 루트 노드로, document 객체를 가리킨다. 이는 브라우저가 렌더링한 HTML 문서 전체를 표현하는 객체로, 전역 객체인 window의 document 프로퍼티에 바인딩되어 있다. HTML 문서당 단 하나의 document 객체만이 존재하며, 이는 모든 자바스크립트 코드가 공유한다.
document 객체는 DOM 트리에서 진입점 역할을 하는데, 이는 모든 노드에 접근하기 위해서는 반드시 문서 노드를 통해야 함을 의미한다. 요소 노드, 어트리뷰트 노드, 텍스트 노드 등 모든 노드에 접근하기 위해서는 먼저 document 객체에 접근해야 한다.
요소 노드(Element Node)의 역할
요소 노드는 HTML 요소를 가리키는 객체로, HTML 문서의 구조를 구성하는 기본 단위다. HTML 요소들 간의 중첩 관계는 요소 노드들 간의 부모-자식 관계로 표현되며, 이를 통해 문서의 구조적인 정보를 표현한다. 요소 노드는 HTML 요소의 종류에 따라 다양한 특성을 가지며, 자식 노드로 다른 요소 노드나 텍스트 노드를 가질 수 있다.
어트리뷰트 노드(Attribute Node)의 특성
어트리뷰트 노드는 HTML 요소의 어트리뷰트를 표현하는 객체다. 다른 노드들과는 달리 어트리뷰트 노드는 요소 노드와 특별한 관계를 가진다. 어트리뷰트 노드는 부모 노드를 가지지 않으며, 요소 노드에 직접적으로 연결되어 있다. 이러한 특성 때문에 어트리뷰트 노드에 접근하거나 조작하기 위해서는 반드시 해당 요소 노드를 통해야 한다.
텍스트 노드(Text Node)의 기능
텍스트 노드는 HTML 요소의 텍스트 콘텐츠를 표현하는 객체다. DOM 트리에서 가장 말단에 위치하는 노드로, 자식 노드를 가질 수 없는 리프 노드의 특성을 가진다. 텍스트 노드는 요소 노드의 자식 노드로만 존재할 수 있으며, 문서의 실질적인 내용을 저장하고 표현하는 역할을 한다. 텍스트 노드에 접근하기 위해서는 먼저 해당 텍스트 노드의 부모 노드인 요소 노드에 접근해야 한다.
노드 객체의 상속 구조
노드 객체의 프로토타입 체인
DOM을 구성하는 노드 객체들은 브라우저 환경에서 제공하는 호스트 객체로, 일반적인 자바스크립트 객체처럼 프로토타입 기반의 상속 구조를 따른다. 모든 노드 객체는 Object, EventTarget, Node 인터페이스를 기본적으로 상속받으며, 각각의 노드 타입에 따라 추가적인 인터페이스를 상속받는다.
예를 들어, input 요소 노드의 경우 HTMLInputElement, HTMLElement, Element, Node, EventTarget, Object의 프로토타입 체인을 형성한다. 이러한 상속 구조를 통해 각각의 노드는 필요한 프로퍼티와 메서드를 사용할 수 있게 된다.
DOM API의 기능과 활용
DOM API는 노드 객체가 자신의 구조와 정보를 제어할 수 있게 해주는 인터페이스다. 이를 통해 노드 객체는 자신의 부모, 형제, 자식 노드를 탐색할 수 있으며, 어트리뷰트와 텍스트를 조작할 수 있다. DOM API는 각 노드 타입별로 필요한 기능을 프로퍼티와 메서드의 형태로 제공하며, 이를 통해 개발자는 HTML 문서의 구조, 내용, 스타일을 동적으로 수정할 수 있다.
노드 객체의 타입에 따라 상속받는 인터페이스가 다르며, 이에 따라 사용할 수 있는 DOM API도 달라진다. 예를 들어 모든 노드는 Node 인터페이스로부터 노드 탐색과 정보 제공 기능을, EventTarget 인터페이스로부터 이벤트 관련 기능을 상속받는다. 이러한 DOM API의 체계적인 구조는 웹 페이지의 동적인 조작을 가능하게 하는 핵심 메커니즘이 된다.
요소 노드 취득
HTML의 구조나 내용, 스타일을 동적으로 조작하기 위해서는 먼저 요소 노드를 취득해야 한다. DOM은 이를 위해 다양한 메서드를 제공하며, 각각의 메서드는 고유한 특성과 활용 방법을 가지고 있다.
id를 이용한 요소 노드 취득
// id 값이 'banana'인 요소 노드를 탐색하여 반환
const $elem = document.getElementById('banana');
Document.prototype.getElementById 메서드는 인수로 전달한 id 어트리뷰트 값을 갖는 하나의 요소 노드를 탐색하여 반환한다. 이 메서드는 반드시 문서 노드인 document를 통해 호출해야 한다.
id 값은 HTML 문서 내에서 유일한 값이어야 하며, class 어트리뷰트와 달리 공백 문자로 구분하여 여러 개의 값을 가질 수 없다. 그러나 HTML 문서 내에 중복된 id 값을 갖는 요소가 여러 개 존재하더라도 어떠한 에러도 발생하지 않는다. 이 경우 메서드는 해당 id 값을 갖는 첫 번째 요소 노드만을 반환한다. 존재하지 않는 id 값으로 요소 노드를 찾으려 할 경우 null을 반환한다.
특이한 점은 HTML 요소에 id 어트리뷰트를 부여하면 id 값과 동일한 이름의 전역 변수가 암묵적으로 선언되고 해당 노드 객체가 할당되는 부수 효과가 있다는 것이다. 단, id 값과 동일한 이름의 전역 변수가 이미 선언되어 있다면 이 전역 변수에 노드 객체가 재할당되지 않는다.
태그 이름을 이용한 요소 노드 취득
// 태그 이름이 'li'인 모든 요소 노드들을 탐색하여 HTMLCollection 객체로 반환
const $elems = document.getElementsByTagName('li');
Document.prototype/Element.prototype.getElementsByTagName 메서드는 인수로 전달한 태그 이름을 갖는 모든 요소 노드들을 탐색하여 반환한다. 이 메서드는 여러 개의 요소 노드 객체를 갖는 HTMLCollection 객체를 반환한다.
getElementsByTagName 메서드는 두 가지 방식으로 사용할 수 있다. Document.prototype의 메서드로 사용할 경우 DOM 전체에서 요소를 탐색하고, Element.prototype의 메서드로 사용할 경우 특정 요소의 자손 노드 중에서만 요소를 탐색한다.
모든 요소 노드를 취득하고 싶을 때는 인수로 '*'를 전달하면 된다. 만약 인수로 전달된 태그 이름을 갖는 요소가 존재하지 않는다면 빈 HTMLCollection 객체를 반환한다.
class를 이용한 요소 노드 취득
// class 값이 'fruit'인 모든 요소 노드를 탐색하여 반환
const $elems = document.getElementsByClassName('fruit');
// class 값이 'fruit apple'인 모든 요소 노드를 탐색하여 반환
const $apples = document.getElementsByClassName('fruit apple');
Document.prototype/Element.prototype.getElementsByClassName 메서드는 인수로 전달한 class 어트리뷰트 값을 갖는 모든 요소 노드들을 탐색하여 반환한다. 이 메서드 역시 HTMLCollection 객체를 반환하며, Document.prototype과 Element.prototype 두 가지 방식으로 사용할 수 있다.
class 어트리뷰트 값은 공백으로 구분하여 여러 개의 클래스를 지정할 수 있다. getElementsByClassName 메서드는 이러한 경우에도 매칭되는 모든 요소 노드를 탐색하여 반환한다. 요소가 존재하지 않는 경우에는 빈 HTMLCollection 객체를 반환한다.
CSS 선택자를 이용한 요소 노드 취득
CSS 선택자는 스타일을 적용하고자 하는 HTML 요소를 특정할 때 사용하는 문법이다. DOM은 이 CSS 선택자 문법을 사용하여 요소 노드를 취득할 수 있는 강력한 메서드를 제공한다.
querySelector 메서드
// class가 'banana'인 첫 번째 요소 노드를 반환
const $elem = document.querySelector('.banana');
// ul 요소의 자식 요소인 li 요소를 모두 탐색하여 첫 번째 요소 반환
const $li = document.querySelector('ul > li');
Document.prototype/Element.prototype.querySelector 메서드는 인수로 전달한 CSS 선택자를 만족시키는 하나의 요소 노드를 탐색하여 반환한다.
여러 개의 요소 노드가 선택자와 매칭되더라도 첫 번째 요소 노드만 반환한다. 만약 매칭되는 요소가 없다면 null을 반환하며, CSS 선택자가 문법에 맞지 않는 경우에는 DOMException 에러가 발생한다.
querySelectorAll 메서드
// ul 요소의 모든 자식 li 요소 노드를 탐색하여 반환
const $elems = document.querySelectorAll('ul > li');
// 취득한 모든 요소 노드의 스타일을 변경
$elems.forEach(elem => elem.style.color = 'red');
Document.prototype/Element.prototype.querySelectorAll 메서드는 인수로 전달한 CSS 선택자를 만족시키는 모든 요소 노드를 탐색하여 반환한다. 이 메서드는 NodeList 객체를 반환하며, 요소가 존재하지 않는 경우 빈 NodeList 객체를 반환한다.
querySelector와 querySelectorAll 메서드는 getElementBy*** 메서드들보다 다소 느린 것으로 알려져 있다. 하지만 CSS 선택자 문법을 사용하여 좀 더 구체적인 조건으로 요소 노드를 취득할 수 있고 일관된 방식으로 요소 노드를 취득할 수 있다는 장점이 있다. 따라서 id 어트리뷰트가 있는 요소를 취득할 때를 제외하고는 querySelector, querySelectorAll 메서드를 사용하는 것이 권장된다.
특정 요소 노드를 취득할 수 있는지 확인
const $apple = document.querySelector('.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
Element.prototype.matches 메서드는 인수로 전달한 CSS 선택자를 통해 특정 요소 노드를 취득할 수 있는지 확인한다. 이 메서드는 이벤트 위임을 사용할 때 특히 유용하다.
HTMLCollection과 NodeList
DOM 컬렉션 객체인 HTMLCollection과 NodeList는 DOM API가 여러 개의 결과값을 반환하기 위한 특별한 객체다. 이 두 객체는 유사 배열 객체이면서 이터러블이라는 공통점을 가지고 있어 for...of 문으로 순회가 가능하고 스프레드 문법을 사용하여 배열로 변환할 수 있다.
HTMLCollection 객체의 특징과 주의점
// class 값이 'red'인 요소를 모두 취득
const $elems = document.getElementsByClassName('red');
// HTMLCollection 객체의 모든 요소의 class 값을 'blue'로 변경
for (let i = 0; i < $elems.length; i++) {
$elems[i].className = 'blue';
}
getElementsByTagName과 getElementsByClassName 메서드가 반환하는 HTMLCollection 객체는 노드 객체의 상태 변화를 실시간으로 반영하는 살아있는(live) DOM 컬렉션 객체다. 이러한 특성은 때때로 의도하지 않은 결과를 가져올 수 있다.
위 예제에서 모든 요소의 class 값이 'blue'로 변경될 것으로 예상하지만, 실제로는 그렇지 않다. HTMLCollection은 실시간으로 노드 객체의 상태를 반영하기 때문에, 첫 번째 요소의 class 값이 변경되면 해당 요소는 더 이상 HTMLCollection에 포함되지 않게 된다. 이로 인해 인덱스가 실시간으로 변경되어 일부 요소가 처리되지 않는 문제가 발생한다.
이러한 문제를 해결하기 위한 방법들이 있다.
1. for 문을 역방향으로 순회
for (let i = $elems.length - 1; i >= 0; i--) {
$elems[i].className = 'blue';
}
2. while 문을 사용하여 객체에 요소가 남아있지 않을 때까지 반복
let i = 0;
while ($elems.length > i) {
$elems[i].className = 'blue';
}
3. HTMLCollection 객체를 배열로 변환하여 사용 (가장 권장되는 방법)
[...$elems].forEach(elem => elem.className = 'blue');
NodeList 객체의 특징
querySelectorAll 메서드가 반환하는 NodeList 객체는 대부분의 경우 노드 객체의 상태 변경을 실시간으로 반영하지 않는 정적(non-live) 객체로 동작한다. 이는 HTMLCollection 객체의 부작용을 해결할 수 있는 장점이 된다.
하지만 주의해야 할 점이 있다. childNodes 프로퍼티가 반환하는 NodeList 객체는 HTMLCollection 객체와 같이 실시간으로 노드 객체의 상태 변경을 반영하는 live 객체로 동작한다. 따라서 이 경우에도 HTMLCollection과 같은 부작용이 발생할 수 있다.
최선의 해결책
// 스프레드 문법을 사용한 배열 변환
const $elems = [...document.getElementsByClassName('red')];
// Array.from을 사용한 배열 변환
const $elems = Array.from(document.querySelectorAll('.red'));
노드 객체의 상태 변경과 상관없이 안전하게 DOM 컬렉션을 사용하려면, HTMLCollection이나 NodeList 객체를 배열로 변환하여 사용하는 것이 가장 권장된다. 배열로 변환하면 배열의 유용한 고차 함수들(forEach, map, filter, reduce 등)을 사용할 수 있으며, 실시간 변경에 따른 부작용도 피할 수 있다.
이렇게 배열로 변환하여 사용하면 DOM 컬렉션 객체의 실시간 변경에 영향을 받지 않고 안전하게 요소를 조작할 수 있다.
노드 탐색
DOM을 조작할 때는 먼저 요소 노드를 취득한 후, 해당 노드를 기점으로 DOM 트리를 옮겨다니며 부모, 형제, 자식 노드 등을 탐색해야 할 때가 있다. DOM은 이러한 노드 탐색을 위해 Node와 Element 인터페이스를 통해 다양한 트리 탐색 프로퍼티를 제공한다.
Node.prototype은 parentNode, previousSibling, firstChild, childNodes와 같은 프로퍼티를 제공하고, Element.prototype은 previousElementSibling, nextElementSibling, children과 같은 프로퍼티를 제공한다. 이러한 노드 탐색 프로퍼티들은 모두 접근자 프로퍼티이며, setter 없이 getter만 존재하는 읽기 전용 프로퍼티다.
공백 텍스트 노드
<ul id="fruits">
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</ul>
HTML 요소 사이에 존재하는 스페이스, 탭, 줄바꿈(개행) 등의 공백 문자는 텍스트 노드를 생성한다. 이를 공백 텍스트 노드라고 하며, 노드를 탐색할 때 이러한 공백 텍스트 노드의 존재를 반드시 고려해야 한다.
위 HTML 코드에서 각 <li> 요소 사이의 줄바꿈과 공백은 공백 텍스트 노드를 생성한다. 이러한 공백 문제를 해결하기 위해 HTML 요소 사이의 공백을 제거할 수 있지만, 가독성이 나빠지므로 권장하지 않는다.
자식 노드 탐색
childNodes와 children 프로퍼티
Node.prototype.childNodes 프로퍼티는 자식 노드를 모두 탐색하여 NodeList 객체로 반환한다. 이는 요소 노드뿐만 아니라 텍스트 노드도 포함한다. 반면, Element.prototype.children 프로퍼티는 자식 노드 중에서 요소 노드만을 탐색하여 HTMLCollection 객체로 반환한다.
첫 번째와 마지막 자식 노드 탐색
const $fruits = document.getElementById('fruits');
// 텍스트 노드를 포함한 모든 자식 노드 탐색
console.log($fruits.childNodes);
// NodeList(7) [text, li.apple, text, li.banana, text, li.orange, text]
// 요소 노드만 탐색
console.log($fruits.children);
// HTMLCollection(3) [li.apple, li.banana, li.orange]
- Node.prototype.firstChild: 첫 번째 자식 노드를 반환 (텍스트 노드 포함)
- Node.prototype.lastChild: 마지막 자식 노드를 반환 (텍스트 노드 포함)
- Element.prototype.firstElementChild: 첫 번째 자식 요소 노드만 반환
- Element.prototype.lastElementChild: 마지막 자식 요소 노드만 반환
자식 노드 존재 확인
const $fruits = document.getElementById('fruits');
// 텍스트 노드를 포함하여 자식 노드의 존재를 확인
console.log($fruits.hasChildNodes()); // true
Node.prototype.hasChildNodes 메서드를 사용하여 자식 노드의 존재 여부를 확인할 수 있다. 이 메서드는 자식 노드가 존재하면 true를, 존재하지 않으면 false를 반환한다.
주의할 점은 hasChildNodes 메서드가 childNodes 프로퍼티와 마찬가지로 텍스트 노드를 포함하여 자식 노드의 존재를 확인한다는 것이다. 따라서 요소 노드만을 확인하고 싶다면 다른 방법을 사용해야 한다.
텍스트 노드를 제외하고 요소 노드만 존재하는지 확인하려면 다음 두 가지 방법 중 하나를 사용할 수 있다.
const $fruits = document.getElementById('fruits');
// 요소 노드만 확인
console.log(!!$fruits.children.length); // true
console.log(!!$fruits.childElementCount); // true
- children.length 프로퍼티 확인
- Element 인터페이스의 childElementCount 프로퍼티 사용
요소 노드의 텍스트 노드 탐색
const $foo = document.getElementById('foo');
console.log($foo.firstChild); // #text "Hello"
요소 노드의 텍스트 노드는 해당 요소 노드의 자식 노드이므로 firstChild 프로퍼티를 사용하여 접근할 수 있다. firstChild 프로퍼티는 첫 번째 자식 노드를 반환하는데, 이는 텍스트 노드이거나 요소 노드일 수 있다.
부모 노드 탐색
const $banana = document.querySelector('.banana');
console.log($banana.parentNode); // ul#fruits
Node.prototype.parentNode 프로퍼티를 사용하여 부모 노드를 탐색할 수 있다. 텍스트 노드는 DOM 트리의 최종단인 리프 노드이므로 부모 노드가 텍스트 노드인 경우는 존재하지 않는다.
형제 노드 탐색
형제 노드는 같은 부모 노드를 갖는 노드를 말한다. DOM에서는 다음과 같은 프로퍼티들을 통해 형제 노드를 탐색할 수 있다.
텍스트 노드를 포함하는 프로퍼티
- Node.prototype.previousSibling: 이전 형제 노드 반환
- Node.prototype.nextSibling: 다음 형제 노드 반환
요소 노드만을 탐색하는 프로퍼티
const $banana = document.querySelector('.banana');
// 요소 노드만 탐색
console.log($banana.previousElementSibling); // li.apple
console.log($banana.nextElementSibling); // li.orange
// 텍스트 노드를 포함하여 탐색
console.log($banana.previousSibling); // #text
console.log($banana.nextSibling); // #text
- Element.prototype.previousElementSibling: 이전 형제 요소 노드 반환
- Element.prototype.nextElementSibling: 다음 형제 요소 노드 반환
주의할 점은 어트리뷰트 노드는 요소 노드와 연결되어 있지만 부모 노드가 같은 형제 노드가 아니므로 이러한 형제 노드 탐색 프로퍼티로는 반환되지 않는다. 이 프로퍼티들은 오직 텍스트 노드나 요소 노드만을 반환한다.
노드 정보 취득
DOM에서 각 노드의 정보를 취득하기 위해서는 Node.prototype이 제공하는 노드 정보 프로퍼티를 사용해야 한다. 이러한 프로퍼티들을 통해 노드의 타입과 이름 등 중요한 정보를 확인할 수 있다.
nodeType 프로퍼티
Node.prototype.nodeType은 노드 객체의 종류, 즉 노드 타입을 나타내는 상수를 반환한다. 이 프로퍼티는 노드 타입을 숫자로 반환하며, 각 숫자는 특정 노드 타입을 나타낸다.
주요 노드 타입의 상수값은 다음과 같다.
// 문서 노드
console.log(document.nodeType); // 9
console.log(document.nodeType === Node.DOCUMENT_NODE); // true
// 요소 노드
const $element = document.getElementById('foo');
console.log($element.nodeType); // 1
console.log($element.nodeType === Node.ELEMENT_NODE); // true
// 텍스트 노드
const $text = $element.firstChild;
console.log($text.nodeType); // 3
console.log($text.nodeType === Node.TEXT_NODE); // true
- Node.ELEMENT_NODE: 1 (요소 노드)
- Node.TEXT_NODE: 3 (텍스트 노드)
- Node.DOCUMENT_NODE: 9 (문서 노드)
nodeName 프로퍼티
Node.prototype.nodeName은 노드의 이름을 문자열로 반환한다. 반환되는 노드의 이름은 노드 타입에 따라 다르다.
요소 노드의 경우
HTML 요소의 태그 이름을 대문자로 반환한다. 예를 들어, <div> 요소는 "DIV", <ul> 요소는 "UL", <li> 요소는 "LI"와 같이 반환된다.
텍스트 노드의 경우
항상 "#text"라는 문자열을 반환한다.
문서 노드의 경우
// 문서 노드
console.log(document.nodeName); // "#document"
// 요소 노드
const $element = document.createElement('div');
console.log($element.nodeName); // "DIV"
// 텍스트 노드
const text = document.createTextNode('Hello');
console.log(text.nodeName); // "#text"
항상 "#document"라는 문자열을 반환한다.
요소 노드의 텍스트 조작
DOM에서 요소 노드의 텍스트를 조작하는 방법은 크게 nodeValue와 textContent 두 가지 프로퍼티를 통해 이루어진다. 이 두 프로퍼티는 모두 setter와 getter를 가진 접근자 프로퍼티로, 텍스트의 읽기와 쓰기가 모두 가능하다.
nodeValue
nodeValue의 특징과 동작
// 문서 노드의 nodeValue 참조
console.log(document.nodeValue); // null
// 요소 노드의 nodeValue 참조
const $foo = document.getElementById('foo');
console.log($foo.nodeValue); // null
// 텍스트 노드의 nodeValue 참조
const $textNode = $foo.firstChild;
console.log($textNode.nodeValue); // 실제 텍스트 내용
Node.prototype.nodeValue 프로퍼티는 노드 객체의 값을 반환하거나 변경할 수 있게 해준다. 여기서 노드 객체의 값이란 텍스트 노드의 텍스트를 의미한다. 텍스트 노드가 아닌 다른 노드(예: 문서 노드나 요소 노드)의 nodeValue를 참조하면 null을 반환한다.
텍스트 조작 방법
// 요소 노드의 텍스트 변경 예시
const $foo = document.getElementById('foo');
const $textNode = $foo.firstChild;
$textNode.nodeValue = 'Changed Text';
요소 노드의 텍스트를 변경하기 위해서는 다음과 같은 단계적 접근이 필요하다.
- 텍스트를 변경하고자 하는 요소 노드를 먼저 취득한다.
- 해당 요소 노드의 텍스트 노드를 탐색한다(firstChild 프로퍼티 사용).
- 탐색한 텍스트 노드의 nodeValue 프로퍼티를 사용하여 텍스트 값을 변경한다.
textContent
textContent의 특징과 동작
<div id="foo">Hello <span>world!</span></div>
console.log(document.getElementById('foo').textContent); // "Hello world!"
Node.prototype.textContent 프로퍼티는 요소 노드의 텍스트와 모든 자손 노드의 텍스트를 모두 취득하거나 변경할 수 있게 해준다. 이는 nodeValue보다 더 간편하고 직관적인 방식으로 텍스트를 다룰 수 있게 해준다.
textContent의 주요 특징
// HTML 마크업이 포함된 문자열 할당
const $foo = document.getElementById('foo');
$foo.textContent = 'Hello <span>world!</span>';
// 결과: 'Hello <span>world!</span>'가 텍스트로 표시됨
- HTML 마크업 무시: 요소 노드의 textContent 프로퍼티를 참조하면 HTML 마크업은 무시하고 텍스트만 반환한다.
- 모든 자손 노드의 텍스트 포함: 특정 요소 노드 하위의 모든 자손 노드들의 텍스트를 모두 포함하여 반환한다.
- 텍스트 설정 시 특징.
- 요소 노드에 문자열을 할당하면 모든 자식 노드가 제거된다.
- 할당한 문자열이 새로운 텍스트로 추가된다.
- HTML 마크업이 포함된 문자열을 할당해도 마크업이 파싱되지 않고 문자 그대로 텍스트로 처리된다.
textContent vs innerText
textContent와 유사한 기능을 하는 innerText 프로퍼티도 있지만, 이는 CSS에 종속적이고 CSS를 고려해야 하므로 textContent보다 느리다. 따라서 일반적으로 textContent 프로퍼티의 사용이 권장된다.
DOM 조작
DOM 조작이란 새로운 노드를 생성하여 DOM에 추가하거나 기존 노드를 삭제 또는 교체하는 것을 말한다. DOM 조작은 웹 페이지의 동적인 변경을 가능하게 하지만, 이로 인해 리플로우와 리페인트가 발생하므로 성능에 영향을 준다. 따라서 DOM 조작을 할 때는 성능 최적화를 고려하여 신중하게 접근해야 한다.
innerHTML
innerHTML의 기본 동작
// HTML 마크업 취득
console.log(document.getElementById('foo').innerHTML);
// 'Hello <span>world!</span>'
// HTML 마크업 변경
document.getElementById('foo').innerHTML = 'Hi <span>there!</span>';
Element.prototype.innerHTML 프로퍼티는 setter와 getter를 모두 가진 접근자 프로퍼티로, 요소 노드의 HTML 마크업을 취득하거나 변경할 수 있다. 이 프로퍼티를 참조하면 요소 노드의 콘텐츠 영역 내의 모든 HTML 마크업을 문자열로 반환한다.
innerHTML의 문제점과 한계
- 보안 취약성: 사용자로부터 입력받은 데이터를 innerHTML에 그대로 할당하면 크로스 사이트 스크립팅(XSS) 공격에 취약하다. HTML 마크업 내에 자바스크립트 악성 코드가 포함되어 있을 경우 실행될 수 있다.
- 비효율적인 업데이트: innerHTML 프로퍼티에 HTML 마크업을 할당하면 요소의 모든 자식 노드를 제거하고 새로운 노드를 생성한다. 이는 유지되어야 할 자식 노드까지도 모두 제거하고 다시 생성하는 비효율을 초래한다.
- 삽입 위치 제한: 새로운 요소를 특정 위치에 삽입하는 것이 불가능하다. 항상 전체 내용을 교체해야 한다.
insertAdjacentHTML
insertAdjacentHTML의 기본 동작
const $foo = document.getElementById('foo');
// 요소 앞에 삽입
$foo.insertAdjacentHTML('beforebegin', '<p>Before element</p>');
// 요소 내부 처음
$foo.insertAdjacentHTML('afterbegin', '<p>Inside element, at the beginning</p>');
// 요소 내부 끝
$foo.insertAdjacentHTML('beforeend', '<p>Inside element, at the end</p>');
// 요소 뒤에 삽입
$foo.insertAdjacentHTML('afterend', '<p>After element</p>');
Element.prototype.insertAdjacentHTML(position, DOMString) 메서드는 기존 요소를 제거하지 않으면서 원하는 위치에 새로운 요소를 삽입할 수 있다. 첫 번째 인수로 위치를, 두 번째 인수로 HTML 마크업 문자열을 전달한다.
insertAdjacentHTML의 장단점
- 장점: innerHTML보다 효율적이고 빠르다. 기존 요소들은 그대로 유지한 채 새로운 요소만 파싱하여 추가하기 때문이다.
- 단점: innerHTML과 마찬가지로 HTML 문자열을 파싱하므로 XSS 공격에 취약하다.
노드 생성과 추가
요소 노드 생성
// li 요소 노드 생성
const $li = document.createElement('li');
Document.prototype.createElement(tagName) 메서드를 사용하여 새로운 요소 노드를 생성할 수 있다.
텍스트 노드 생성
// 텍스트 노드 생성
const textNode = document.createTextNode('Hello World');
Document.prototype.createTextNode(text) 메서드로 텍스트 노드를 생성한다.
노드 추가
// 완성된 요소를 DOM에 추가하는 예시
const $fruits = document.getElementById('fruits');
const $li = document.createElement('li');
const textNode = document.createTextNode('Apple');
// 텍스트 노드를 li 요소 노드의 자식으로 추가
$li.appendChild(textNode);
// li 요소 노드를 fruits 요소 노드의 마지막 자식으로 추가
$fruits.appendChild($li);
Node.prototype.appendChild() 메서드를 사용하여 생성한 노드를 DOM에 추가할 수 있다.
복수의 노드 생성과 추가
const $fruits = document.getElementById('fruits');
const $fragment = document.createDocumentFragment();
['Apple', 'Banana', 'Orange'].forEach(fruit => {
const $li = document.createElement('li');
$li.textContent = fruit;
$fragment.appendChild($li);
});
// DocumentFragment를 DOM에 추가하면 자신은 사라지고
// 자신의 자식 노드만 DOM에 추가된다
$fruits.appendChild($fragment);
여러 노드를 한 번에 추가할 때는 DocumentFragment를 사용하면 성능상 이점을 얻을 수 있다. DocumentFragment는 DOM과 별도로 존재하는 경량화된 문서 객체로, 여러 노드를 담아두는 임시 컨테이너 역할을 한다.
이 방식의 장점...
- DOM 변경이 한 번만 발생한다.
- 리플로우와 리페인트도 한 번만 실행된다.
- 불필요한 중간 컨테이너 요소가 생성되지 않는다.
노드 삽입
노드를 삽입하는 데는 크게 두 가지 방법이 있다.
1. 마지막 노드로 추가하는 appendChild
const $fruits = document.getElementById('fruits');
const $li = document.createElement('li');
$li.textContent = 'Apple';
// 마지막 자식 노드로 추가
$fruits.appendChild($li);
2. 특정 위치에 삽입하는 insertBefore
const $fruits = document.getElementById('fruits');
const $li = document.createElement('li');
$li.textContent = 'Orange';
// $fruits의 첫 번째 자식 노드 앞에 삽입
$fruits.insertBefore($li, $fruits.firstElementChild);
insertBefore 메서드 사용 시 주의사항
- 두 번째 인수로 전달한 노드는 반드시 insertBefore를 호출한 노드의 자식이어야 한다.
- 두 번째 인수가 null이면 appendChild처럼 동작하여 마지막에 추가된다.
노드 이동
const $fruits = document.getElementById('fruits');
const [$apple, $banana] = $fruits.children;
// $apple을 마지막으로 이동
$fruits.appendChild($apple);
// $banana를 맨 앞으로 이동
$fruits.insertBefore($banana, $fruits.firstElementChild);
이미 존재하는 노드를 appendChild나 insertBefore로 다시 추가하면 해당 노드는 현재 위치에서 새로운 위치로 이동한다.
노드 복사
const $fruits = document.getElementById('fruits');
const $apple = $fruits.firstElementChild;
// 얕은 복사 - 노드 자체만 복사
const $shallowClone = $apple.cloneNode(false);
// 깊은 복사 - 모든 자손 노드까지 복사
const $deepClone = $fruits.cloneNode(true);
Node.prototype.cloneNode 메서드를 사용하여 노드를 복사할 수 있다. 매개변수로 true를 전달하면 깊은 복사를, false를 전달하면 얕은 복사를 수행한다.
노드 교체
const $fruits = document.getElementById('fruits');
const $newChild = document.createElement('li');
$newChild.textContent = 'Banana';
// 첫 번째 자식 노드를 $newChild로 교체
$fruits.replaceChild($newChild, $fruits.firstElementChild);
replaceChild 메서드를 사용하여 특정 자식 노드를 다른 노드로 교체할 수 있다.
노드 삭제
const $fruits = document.getElementById('fruits');
// 마지막 자식 노드 삭제
$fruits.removeChild($fruits.lastElementChild);
removeChild 메서드를 사용하여 특정 자식 노드를 DOM에서 삭제할 수 있다.
주의사항
- removeChild 메서드의 인수로 전달할 노드는 반드시 removeChild를 호출한 노드의 자식 노드여야 한다.
- 존재하지 않는 노드를 삭제하려 하면 에러가 발생한다.
어트리뷰트
HTML 요소의 동작을 제어하는 어트리뷰트는 DOM에서 특별한 방식으로 관리되고 조작된다. 어트리뷰트는 요소의 초기 상태와 현재 상태를 모두 관리해야 하는 복잡한 메커니즘을 가지고 있다.
어트리뷰트 노드와 attributes 프로퍼티
const $input = document.getElementById('user');
const { attributes } = $input;
// NamedNodeMap {0: id, 1: type, 2: value, id: id, type: type, value: value, length: 3}
console.log(attributes);
// 개별 어트리뷰트 값 접근
console.log(attributes.id.value); // "user"
console.log(attributes.type.value); // "text"
console.log(attributes.value.value); // "initial value"
HTML 문서가 파싱될 때, HTML 요소의 어트리뷰트는 어트리뷰트 노드로 변환되어 요소 노드와 연결된다. 각각의 어트리뷰트는 하나의 어트리뷰트 노드를 생성하며, 이 노드들은 NamedNodeMap이라는 객체에 담겨 요소 노드의 attributes 프로퍼티에 저장된다.
HTML 어트리뷰트 조작
const $input = document.getElementById('user');
// 어트리뷰트 값 취득
const value = $input.getAttribute('value');
// 어트리뷰트 값 변경
$input.setAttribute('value', 'new value');
// 어트리뷰트 존재 확인
const hasValue = $input.hasAttribute('value');
// 어트리뷰트 제거
$input.removeAttribute('value');
Element.prototype이 제공하는 메서드들을 통해 어트리뷰트를 쉽게 조작할 수 있다.
HTML 어트리뷰트 vs. DOM 프로퍼티
요소 노드는 두 가지 상태를 관리해야 한다.
- 초기 상태(어트리뷰트 노드가 관리)
- 최신 상태(DOM 프로퍼티가 관리)
초기 상태 관리 (어트리뷰트)
const $input = document.getElementById('user');
// 초기 상태 값 취득
console.log($input.getAttribute('value')); // 항상 초기 값 반환
HTML 어트리뷰트는 요소의 초기 상태를 정의하며, 이 값은 변하지 않는다. 이는 페이지를 처음 로드하거나 새로고침할 때의 상태를 유지하기 위해 필요하다.
최신 상태 관리 (DOM 프로퍼티)
const $input = document.getElementById('user');
// 최신 상태 값 취득/변경
console.log($input.value); // 현재 값 반환
$input.value = 'new value'; // 값 변경
DOM 프로퍼티는 사용자의 입력 등으로 인한 요소의 현재 상태를 관리한다. 이 값은 실시간으로 변경될 수 있다.
HTML 어트리뷰트와 DOM 프로퍼티의 대응 관계
어트리뷰트와 프로퍼티는 항상 1:1로 대응하지 않으며, 이름과 값의 관계도 다양한 형태를 가진다.
1. 기본적인 1:1 대응 관계
// id 어트리뷰트와 id 프로퍼티는 1:1로 대응하고 항상 동일한 값을 유지한다
const $element = document.getElementById('user');
$element.id = 'new-id';
console.log($element.getAttribute('id')); // "new-id"
2. 상태 분리 관계
// input의 value는 어트리뷰트와 프로퍼티가 다른 값을 가질 수 있다
const $input = document.getElementById('user-input');
console.log($input.getAttribute('value')); // 초기값
console.log($input.value); // 현재값
3. 이름이 다른 대응 관계
// class 어트리뷰트는 className, classList 프로퍼티와 대응
// for 어트리뷰트는 htmlFor 프로퍼티와 대응
const $label = document.querySelector('label');
$label.htmlFor = 'username'; // for 어트리뷰트 값 변경
DOM 프로퍼티 값의 타입
const $checkbox = document.querySelector('input[type=checkbox]');
// 어트리뷰트 값은 문자열
console.log($checkbox.getAttribute('checked')); // ""
// DOM 프로퍼티 값은 불리언
console.log($checkbox.checked); // true
어트리뷰트의 값은 항상 문자열이지만, DOM 프로퍼티는 다양한 타입의 값을 가질 수 있다.
data 어트리뷰트와 dataset 프로퍼티
HTML5에서는 사용자 정의 데이터를 저장하기 위한 data 어트리뷰트를 제공한다. 이는 자바스크립트와 HTML 사이의 데이터 교환을 용이하게 한다.
data 어트리뷰트 정의
<div id="user"
data-user-id="123"
data-user-role="admin"
data-last-login="2024-04-10">
User Info
</div>
dataset 프로퍼티를 통한 접근
const $user = document.getElementById('user');
const userData = $user.dataset;
// data 어트리뷰트 값 읽기
console.log(userData.userId); // "123"
console.log(userData.userRole); // "admin"
console.log(userData.lastLogin); // "2024-04-10"
// data 어트리뷰트 값 수정
userData.userRole = 'editor'; // data-user-role 변경
// 새로운 data 어트리뷰트 추가
userData.userStatus = 'active'; // data-user-status 추가
명명 규칙과 주의사항
- data- 접두사 다음에 오는 이름은 케밥 케이스(kebab-case)로 작성한다.
- dataset 프로퍼티로 접근할 때는 카멜 케이스(camelCase)로 변환된다.
- 대소문자를 구분한다.
- 숫자로 시작할 수 없다.
스타일
DOM을 통해 HTML 요소의 스타일을 동적으로 조작하는 방법은 크게 세 가지가 있다. 인라인 스타일을 직접 조작하는 방법, 클래스를 통한 조작 방법, 그리고 계산된 스타일을 참조하는 방법이다. 각각의 방법은 서로 다른 특징과 활용 사례를 가진다.
인라인 스타일 조작
const $div = document.querySelector('div');
// 인라인 스타일 변경
$div.style.backgroundColor = 'yellow';
$div.style.width = '200px';
$div.style.height = '100px';
HTMLElement.prototype.style 프로퍼티를 통해 요소의 인라인 스타일을 직접 조작할 수 있다. 이 프로퍼티는 getter와 setter를 모두 가진 접근자 프로퍼티로, CSSStyleDeclaration 타입의 객체를 반환한다.
CSSStyleDeclaration 객체의 특징
- CSS 프로퍼티에 대응하는 프로퍼티들을 가지고 있다.
- CSS의 케밥 케이스(kebab-case)를 자바스크립트의 카멜 케이스(camelCase)로 변환해서 사용한다.
- 'background-color' → 'backgroundColor'
- 'border-radius' → 'borderRadius'
- 케밥 케이스를 그대로 사용하고 싶다면 대괄호 표기법을 사용할 수 있다.
$div.style['background-color'] = 'yellow';
클래스 조작
className을 통한 조작
// CSS 정의
.box { width: 100px; }
.red { color: red; }
.blue { color: blue; }
// HTML 요소
<div class="box red">Hello</div>
// JavaScript
const $div = document.querySelector('div');
console.log($div.className); // "box red"
// 클래스 변경
$div.className = 'box blue'; // red를 blue로 변경
Element.prototype.className 프로퍼티를 사용하면 class 어트리뷰트 값을 문자열로 취득하거나 변경할 수 있다.
classList를 통한 더 세밀한 제어
const $div = document.querySelector('div');
// 클래스 추가
$div.classList.add('new-class');
// 클래스 제거
$div.classList.remove('old-class');
// 클래스 토글 (있으면 제거, 없으면 추가)
$div.classList.toggle('active');
// 클래스 존재 여부 확인
if ($div.classList.contains('active')) {
// 처리 로직
}
// 클래스 교체
$div.classList.replace('old-class', 'new-class');
Element.prototype.classList 프로퍼티는 DOMTokenList 객체를 반환하며, 이를 통해 클래스를 더 세밀하게 조작할 수 있다.
요소에 적용되어 있는 CSS 스타일 참조
style 프로퍼티만으로는 인라인 스타일만 참조할 수 있다는 한계가 있다. 요소에 실제로 적용된 모든 CSS 스타일을 확인하려면 window.getComputedStyle 메서드를 사용해야 한다.
getComputedStyle 메서드의 활용
const $element = document.querySelector('.target');
const computedStyle = window.getComputedStyle($element);
// 최종적으로 적용된 스타일 값들을 확인할 수 있다
console.log(computedStyle.backgroundColor);
console.log(computedStyle.width);
console.log(computedStyle.height);
getComputedStyle 메서드는 다음과 같은 스타일 정보를 모두 포함한다:
- 인라인 스타일
- 내부/외부 스타일시트의 스타일
- 상속된 스타일
- 기본 스타일
의사 요소 스타일 참조
// CSS
.box::before {
content: 'Hello';
color: red;
}
// JavaScript
const $box = document.querySelector('.box');
const beforeStyle = window.getComputedStyle($box, '::before');
console.log(beforeStyle.content); // "Hello"
console.log(beforeStyle.color); // "rgb(255, 0, 0)"
getComputedStyle 메서드의 두 번째 인수로 의사 요소를 지정하면, 해당 의사 요소의 스타일을 참조할 수 있다.
스타일 조작 방식의 비교와 활용
인라인 스타일 조작의 특징
- 장점
- 직접적이고 즉각적인 스타일 적용이 가능하다.
- 개별 스타일 속성에 대한 세밀한 제어가 가능하다.
- 단점
- CSS의 캡슐화를 해치고 유지보수를 어렵게 만들 수 있다.
- 스타일 우선순위가 높아 다른 스타일을 덮어쓸 수 있다.
클래스 기반 스타일 조작의 특징
- 장점
- CSS의 캡슐화를 유지할 수 있다.
- 여러 스타일을 한 번에 적용하기 쉽다.
- 스타일 재사용이 용이하다.
- 단점
- 클래스에 정의된 스타일만 사용할 수 있다.
- 동적인 값(예: 사용자 입력에 따른 너비 조절)을 적용하기는 불편하다.
실무적 권장사항
- 기본적인 스타일링은 CSS 클래스를 통해 관리한다.
- 동적으로 변경되는 소수의 스타일 속성만 인라인 스타일로 조작한다.
- 스타일 참조가 필요한 경우 getComputedStyle을 활용한다.
요약
DOM의 개념과 구조
- DOM은 HTML 문서의 계층적 구조와 정보를 표현하는 트리 자료구조다.
- 브라우저의 렌더링 엔진이 HTML을 파싱하여 DOM을 생성한다.
- DOM은 HTML 문서를 조작할 수 있는 API를 제공한다.
- DOM을 통해 자바스크립트로 HTML 요소를 동적으로 조작할 수 있다.
노드의 이해
- HTML 요소는 파싱되어 DOM의 요소 노드로 변환된다.
- 어트리뷰트는 어트리뷰트 노드로, 텍스트는 텍스트 노드로 변환된다.
- 노드 타입에는 문서 노드, 요소 노드, 어트리뷰트 노드, 텍스트 노드 등이 있다.
- 문서 노드: DOM 트리의 최상위 루트 노드
- 요소 노드: HTML 요소를 표현
- 어트리뷰트 노드: HTML 요소의 속성을 표현
- 텍스트 노드: HTML 요소의 텍스트를 표현
- 노드들은 계층적인 구조를 이루며 부모-자식 관계를 형성한다.
노드 객체의 상속 구조
- 모든 노드는 Object, EventTarget, Node 인터페이스를 상속받는다.
- 요소 노드는 Element, HTMLElement 등의 인터페이스도 상속받는다.
- 각 노드 타입별로 고유한 기능을 제공하는 프로토타입 체인을 형성한다.
- 이 상속 구조를 통해 DOM API의 기능들을 사용할 수 있다.
요소 노드 취득
- getElementById: id를 이용한 단일 요소 노드 취득
- getElementsByTagName: 태그 이름으로 여러 요소 노드 취득
- getElementsByClassName: class로 여러 요소 노드 취득
- querySelector/querySelectorAll: CSS 선택자로 요소 노드 취득
- HTMLCollection과 NodeList는 DOM 컬렉션 객체를 반환한다.
노드 탐색과 정보
- parentNode, childNodes 등으로 노드 간의 관계를 탐색할 수 있다.
- firstChild, lastChild 등으로 특정 자식 노드에 접근할 수 있다.
- nodeType, nodeName으로 노드의 정보를 확인할 수 있다.
- 공백 텍스트 노드의 존재를 항상 고려해야 한다.
요소 노드의 텍스트 조작
- nodeValue: 텍스트 노드의 값을 취득하거나 변경
- textContent: 요소 노드의 모든 텍스트를 취득하거나 변경
- textContent는 HTML 마크업을 무시하고 텍스트만 다룬다.
- innerHTML은 HTML 마크업을 포함한 내용을 다룰 수 있다.
DOM 조작
- createElement로 새로운 요소 노드 생성
- appendChild, insertBefore로 노드 추가
- removeChild로 노드 삭제
- replaceChild로 노드 교체
- DocumentFragment를 활용한 효율적인 DOM 조작
어트리뷰트 조작
- getAttribute/setAttribute로 어트리뷰트 값 취득/변경
- HTML 어트리뷰트는 초기 상태를, DOM 프로퍼티는 최신 상태를 관리
- data 어트리뷰트와 dataset 프로퍼티로 사용자 정의 데이터 관리
- 어트리뷰트와 프로퍼티의 대응 관계 이해 필요
스타일 조작
- style 프로퍼티로 인라인 스타일 조작
- className과 classList로 클래스 기반 스타일 조작
- getComputedStyle로 적용된 스타일 참조
- CSS의 캡슐화와 유지보수성을 고려한 스타일 조작 방식 선택
성능과 최적화
- DOM 조작은 브라우저의 리플로우와 리페인트를 발생시킨다.
- DocumentFragment를 활용하여 DOM 조작 최소화
- 불필요한 중복 조작을 피하고 일괄 처리하는 것이 좋다.
- innerHTML 사용 시 크로스 사이트 스크립팅(XSS) 공격 주의
예상문제 [🔥]
https://github.com/junh0328/prepare_frontend_interview?tab=readme-ov-file
DOM이 뭔가요?
DOM(Document Object Model)은 HTML 문서의 계층적 구조와 정보를 표현하고 이를 제어할 수 있는 API를 제공하는 트리 자료구조입니다.
브라우저의 렌더링 엔진이 HTML 문서를 파싱하면, 브라우저가 이해할 수 있는 자료구조인 DOM을 생성합니다. 이를 통해 자바스크립트가 HTML 요소들을 동적으로 조작할 수 있게 됩니다.
간단한 예를 들어보면, HTML 문서에서 <div>Hello World</div>라는 코드가 있다면, 이것이 DOM으로 변환될 때 div라는 요소 노드가 생성되고, 'Hello World'라는 텍스트는 텍스트 노드로 변환되어 요소 노드의 자식 노드로 구성됩니다.
이렇게 생성된 DOM을 통해 자바스크립트는 document.getElementById()나 querySelector() 같은 DOM API를 사용하여 HTML 요소를 검색하고, 내용을 변경하거나, 스타일을 수정하는 등의 동적인 조작을 할 수 있습니다.
DOM을 구성하는 건 뭐가 있나요?
DOM은 크게 네 가지 주요 노드 타입으로 구성됩니다.
첫째, 문서 노드(Document Node)입니다. 이는 DOM 트리의 최상위에 존재하는 루트 노드로, document 객체를 가리킵니다. 모든 자바스크립트 코드는 이 document 객체를 통해 DOM에 접근할 수 있습니다.
둘째, 요소 노드(Element Node)입니다. HTML 요소를 표현하는 객체로, DOM의 구조를 표현하는 핵심 노드입니다. 예를 들어 <div>, <p>, <span> 같은 HTML 태그들이 요소 노드로 변환됩니다.
셋째, 어트리뷰트 노드(Attribute Node)입니다. HTML 요소의 속성을 표현하는 객체입니다. 예를 들어 <div class="container">에서 class="container"가 어트리뷰트 노드가 됩니다. 특이한 점은 어트리뷰트 노드는 요소 노드에 직접 연결되어 있고 부모 노드를 가지지 않습니다.
넷째, 텍스트 노드(Text Node)입니다. HTML 요소 내의 텍스트를 표현하는 객체로, DOM 트리의 최종단인 리프 노드입니다. 예를 들어 <p>Hello</p>에서 'Hello'가 텍스트 노드가 됩니다.
이러한 노드들은 계층적인 관계를 가지며, 부모-자식 관계를 형성하여 트리 구조를 이룹니다. 이 구조를 통해 우리는 자바스크립트로 DOM을 탐색하고 조작할 수 있게 됩니다.
'🧱 프론트엔드 주제 > JavaScript' 카테고리의 다른 글
[모던 자바스크립트 Deep Dive] 41장 - 타이머 (0) | 2024.11.12 |
---|---|
[모던 자바스크립트 Deep Dive] 40장 - 이벤트 (0) | 2024.11.11 |
[모던 자바스크립트 Deep Dive] 38장 - 브라우저의 렌더링 과정 (3) | 2024.10.31 |
[모던 자바스크립트 Deep Dive] 36장 - 배열 디스트럭처링 할당 (0) | 2024.10.25 |
[모던 자바스크립트 Deep Dive] 35장 - 스프레드 문법 (1) | 2024.10.22 |