38장에서 다룬 브라우저 렌더링 엔진은 HTML 문서를 파싱해 DOM(Document Object Model)
을 생성한다.
DOM의 구조와, DOM이 갖고 있는 DOM API에는 어떤 것이 있는지 한 번 살펴보자. 🧐
HTML 요소(HTML element)는 렌더링 엔진을 통해 DOM을 구성하는 요소 노드
객체로 변환된다. 이때 HTML 요소의 어트리뷰트는 어트리뷰트 노드
로, 텍스트 콘텐츠는 텍스트 노드
로 변환된다.
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div>
<h1>Cities</h1>
<ul>
<li id="one" class="red">Seoul</li>
<li id="two" class="red">London</li>
<li id="three" class="red">Newyork</li>
<li id="four">Tokyo</li>
</ul>
</div>
</body>
</html>
DOM / 그림출처: poiemaweb
노드 객체 중 문서 노드는 DOM 트리의 최상위에 존재하는 루트 노드로서 document
객체를 가리킨다. document
객체는 브라우저가 렌더링한 HTML 문서 전체를 가리키는 객체로서 전역 객체 window
의 document 프로퍼티에 바인딩되어 있다.
문서노드는 DOM 트리의 루트 노드이기 때문에 요소/어트리뷰트/텍스트 노드에 접근하려면 문서 노드를 통해야 한다.
노드 객체는 ECMAScript 사양에 정의된 표준 빌트인 객체가 아니라 브라우저 환경에서 추가적으로 제공하는 호스트 객체다. 하지만 노드 객체도 자바스크립트 객체이기 때문에 Object
를 상속받는다.
모든 노드 객체는 Object
, EventTarget
, Node
인터페이스를 상속받는다. 이벤트와 관련된 기능은 EventTarget
인터페이스가 제공하고, 노드로서 트리 탐색이나 노드 정보 제공 등의 노드 관련 기능은 Node
인터페이스가 제공한다.
또한 문서 노드
는 Document, HTMLDocument 인터페이스를 상속받고, 요소 노드
는 Element, HTMLElement 인터페이스를 상속받는다. 어트리뷰트 노드
는 Attr, 텍스트 노드
는 CharacterData 인터페이스를 각각 상속받는다.
DOM
은 HTML의 계층적인 구조와 정보를 표현하는 것은 물론, 노드 타입에 따라 필요한 기능을 프로퍼티와 메서드의 집합인 DOM API로 제공한다. 이 DOM API를 통해 HTML의 구조나 내용 또는 스타일 등을 동적으로 조작할 수 있다.
요소 노드의 취득은 HTML 요소를 조작하는 시작점이다. 이를 위해 DOM은 요소 노드를 취득할 수 있는 다양한 메서드를 제공한다.
🍭 Document.getElementById()
인수로 전달한 id 값을 갖는 하나의 요소 노드를 탐색해 반환한다. 만약 중복된 id 값을 갖는 요소가 여러 개가 있다면 첫 번째 요소 노드만 반환한다. 또한 해당되는 노드가 없다면 null을 반환한다.
<html>
<body>
<ul>
<li id="banana">Banana</li>
</ul>
<script>
const $elem = document.getElementById('banana');
$elem.style.color = 'red';
</script>
</body>
</html>
🍭 Document.getElementsByTagName()
인수로 전달한 태그 이름을 갖는 모든 요소 노드들을 탐색하여 반환한다.
<html>
<body>
<ul>
<li id="banana">Banana</li>
</ul>
<script>
const $elems = document.getElementsByTagName('li');
[...$elems].forEach(elem => { elem.style.color = 'red'; });
// 모든 요소 노드를 탐색하여 반환
const $all = document.getElementsByTagName('*');
</script>
</body>
</html>
이 밖에 getElementsByClassName
, querySelector
메서드 등을 통해 요소 노드를 얻을 수 있다.
DOM 트리 상의 노드를 탐색할 수 있도록 Node
, Element
인터페이스는 트리 탐색 프로퍼티를 제공한다.
Node 인터페이스에는 parentNode
, previousSibling
, nextSibling
, firstChild
, lastChild
, childNodes
프로퍼티를 제공하고 Element 인터페이스에서는 previousElementSibling
, nextElementSibling
, firstElementChild
, lastElementChild
, children
프로퍼티를 제공한다.
firstChild
와 firstElementChild
프로퍼티는 첫 번째 자식 노드를 반환한다는 공통점이 있지만, firstChild
의 경우 반환한 노드는 요소 노드이거나 텍스트 노드이고 firstElementChild
가 반환한 노드는 오직 요소 노드만을 반환한다는 차이점이 있다.
<html>
<body>
<ul id="fruits">
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</ul>
<script>
const $fruits = document.getElementById('fruits');
console.log($fruits.firstChild); // #text
console.log($fruits.firstElementChild) // li.apple
console.log($fruits.lastChild); // #text
console.log($fruits.lastElementChild) // li.orange
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]
</script>
</body>
</html>
firstChild
프로퍼티를 이용해 결과를 출력하면 텍스트 노드가 나오는 것을 알 수 있다. 이 텍스트 노드는 공백 텍스트 노드로, HTML 요소 사이의 스페이스나 탭, 줄바꿈 등의 공백 문자가 텍스트 노드로 변환된다.
공백 텍스트 노드를 제거하기 위해선 HTML 문서를 다음과 같이 작성해야 한다.
...
<ul id="fruits"><li
class="apple">Apple</li><li
class="banana">Banana</li><li
class="orange">Orange</li></ul>
...
하지만 인위적으로 HTML 문서의 공백 문자를 제거하면 가독성이 좋지 않기 때문에 위와 같은 작성 방식은 권장되지 않는다. 다만, HTML 요소 사이의 공백 문자는 공백 텍스트 노드를 생성한다는 것을 유의해두고 노드 탐색 프로퍼티를 사용하자.
다음과 같은 DOM API를 이용해 새로운 노드를 생성해 DOM에 추가하거나 기존 노드를 삭제 또는 교체할 수 있다.
노드 생성
Document.createElement, Document.createTextNode, Document.createDocumentFragment
노드 삽입
Node.appendChild, Node.insertBefore
노드 복사
Node.cloneNode
노드 교체
Node.replaceChild
노드 삭제
Node.removeChild
DOM 조작을 통해 DOM에 새로운 노드가 추가되거나 삭제되면 리플로우와 리페인트가 발생하기 때문에, 복잡한 콘텐츠를 다루는 DOM 조작은 성능 최적화를 위해 주의해서 다루어야 한다.