탐색으로 돌아가기
Browser11 / 24 단계

DOM

DOM 트리

문서의 구조를 트리로 나타낸 것. 각 요소가 노드가 되고, JS는 이 트리를 통해 화면을 바꾼다.

DOM(Document Object Model)은 HTML/XML 문서를 메모리 상의 논리적 트리(tree)로 표현한 것으로, 각 가지가 노드(node)에서 끝나고 각 노드는 객체다. 트리의 루트는 Document 노드이며 전역 변수 document로 노출되고, 그 아래로 요소(Element), 텍스트(Text), 주석(Comment), 속성(Attribute) 등 여러 노드 타입이 부모/자식/형제 관계로 연결된다. 각 노드 타입은 nodeType 숫자로 구분되는데 Element=1, Text=3, Comment=8, Document=9 등이다. JavaScript는 querySelector, getElementById, createElement, appendChild 같은 DOM API로 이 트리를 조회·수정하고, addEventListener로 이벤트를 등록해 사용자 상호작용을 처리한다. 컬렉션에는 두 종류가 있어, HTMLCollection과 getElementsByTagName류가 반환하는 live 컬렉션은 문서가 바뀌면 자동으로 갱신되고, querySelectorAll이 반환하는 static NodeList는 조회 시점의 스냅샷으로 고정된다. Shadow DOM은 커스텀 엘리먼트 내부에 캡슐화된 별도 DOM 서브트리(ShadowRoot)를 붙여 스타일과 마크업을 외부로부터 격리하는 웹 컴포넌트 기술이다.

내부 구성

Document 객체 (Document Node)
DOM 트리의 루트 노드(nodeType 9). 전역 document로 노출되며 요소 조회·생성 API의 진입점이다
Element 노드 (Element Node)
HTML 태그를 나타내는 노드(nodeType 1). 속성·자식을 가지며 대부분의 DOM 조작 대상이다
Text 노드 (Text Node)
요소 안의 실제 텍스트 콘텐츠를 담는 노드(nodeType 3). 공백·줄바꿈도 텍스트 노드가 된다
Comment 노드 (Comment Node)
HTML 주석을 나타내는 노드(nodeType 8). 렌더링되지 않지만 DOM 트리에는 존재한다
Attribute (Attr)
요소의 속성을 표현. 현대 DOM에서는 트리 노드가 아니라 element.attributes(NamedNodeMap)로 접근한다
트리 관계 (Parent/Child/Sibling)
parentNode, childNodes, firstChild, nextSibling 등으로 노드 간 계층·순서 관계를 표현한다
DOM API (querySelector, getElementById, createElement 등)
트리를 탐색·생성·삽입·삭제하는 인터페이스. CSS 선택자 기반 조회와 명령형 조작을 제공한다
이벤트 시스템 (Events / EventTarget)
addEventListener로 핸들러를 등록하고 capture→target→bubble 3단계로 이벤트를 전파해 상호작용을 처리한다
Live 컬렉션 (HTMLCollection / live NodeList)
getElementsByTagName·children 등이 반환. 문서가 바뀌면 자동으로 갱신되는 동적 컬렉션이다
Static 컬렉션 (static NodeList)
querySelectorAll이 반환. 호출 시점의 스냅샷으로 고정되어 이후 문서 변경을 반영하지 않는다
Shadow DOM (ShadowRoot)
element.attachShadow로 부착하는 캡슐화된 서브트리. 스타일·마크업을 외부로부터 격리하는 웹 컴포넌트의 핵심이다
DocumentFragment
부모가 없는 경량 임시 컨테이너. 여러 노드를 모아 한 번에 삽입해 리플로우를 줄이는 데 쓴다

핵심 포인트

  • DOM은 문서를 노드 객체의 트리로 표현하며 루트는 Document 노드(전역 document)다
  • 노드 타입은 nodeType 숫자로 구분된다: Element=1, Text=3, Comment=8, Document=9 등
  • 노드는 부모/자식/형제(parentNode·childNodes·nextSibling 등) 관계로 연결된 계층 구조를 이룬다
  • querySelector·getElementById·createElement·appendChild 등 DOM API로 트리를 조회·조작한다
  • 이벤트는 addEventListener로 등록되고 capture→target→bubble 단계로 전파된다
  • live 컬렉션(HTMLCollection, getElementsBy*)은 문서 변경에 자동 반영, static NodeList(querySelectorAll)는 스냅샷으로 고정된다
  • Shadow DOM(ShadowRoot)은 캡슐화된 서브트리로 스타일·구조를 외부와 격리하는 웹 컴포넌트 기반이다

심화

면접에서 실력을 가르는 대표 질문이 'live 컬렉션과 static 컬렉션의 차이, 그리고 왜 성능에 영향을 주는가'이다. getElementsByTagName이 반환하는 HTMLCollection은 live라서, `for` 루프 안에서 DOM에 요소를 추가하면 컬렉션 length가 계속 늘어나 무한 루프에 빠질 수 있고, 매 접근마다 문서를 다시 질의하므로 반복 접근이 느릴 수 있다. 반면 querySelectorAll의 NodeList는 static 스냅샷이라 예측 가능하고 forEach도 지원한다. 또 흔한 함정으로 childNodes(모든 노드 타입, 공백 Text 노드 포함)와 children(Element만) 차이, NodeList와 HTMLCollection의 순회 메서드 차이(NodeList만 forEach 내장)를 정확히 구분할 수 있어야 한다. 또 하나 깊은 주제는 'DOM은 언어 중립적 명세이며 JavaScript 객체가 아니다'라는 점이다. DOM은 WHATWG/W3C가 정의한 인터페이스 명세로, 브라우저에서는 C++로 구현된 Blink 객체를 V8이 바인딩으로 감싸 JS에 노출한다. 그래서 DOM 접근은 사실상 JS 힙을 넘어 렌더링 엔진 경계를 건너는 비용이 크고, 이것이 '레이아웃 스래싱(layout thrashing)'—읽기(offsetHeight 등)와 쓰기를 번갈아 하면 강제 동기 레이아웃이 반복되는 문제—의 근본 배경이다. 실무에서는 읽기를 모아서 하고 쓰기를 모아서 하거나 DocumentFragment로 오프-트리에서 조립한 뒤 한 번에 붙여 리플로우를 최소화한다. Shadow DOM의 캡슐화도 단순 스타일 격리를 넘어, 이벤트 리타게팅(event retargeting)으로 shadow 경계를 넘는 이벤트의 target을 호스트로 바꿔 내부 구현을 감추는 등 미묘한 동작을 이해해야 한다.

쉽게 말하면 문서의 목차·구조도를 나무 형태로 그려 놓은 것.

면접 예상 질문

#DOM#트리#노드#querySelector