Document Object Model (DOM)/Anatomy of the DOM

김동현·2026년 4월 7일

개념과 사용법 (Concepts and usage)

문서 객체 모델(DOM)은 웹 문서를 위한 프로그래밍 인터페이스예요. 프로그램이 문서의 구조, 스타일, 그리고 콘텐츠를 변경할 수 있도록 페이지를 나타내 주죠. DOM은 문서를 노드(node)와 객체(object)로 표현하는데, 이렇게 해서 프로그래밍 언어가 페이지와 상호작용할 수 있게 해준답니다.

웹 페이지는 브라우저 창에 표시될 수도 있고 HTML 소스로 볼 수도 있는 문서예요. 두 경우 모두 같은 문서지만, 문서 객체 모델(DOM) 표현을 사용하면 이 문서를 마음대로 조작할 수 있어요. 웹 페이지를 객체 지향적으로 표현한 것이기 때문에, 자바스크립트 같은 스크립트 언어를 사용해서 수정이 가능하답니다.

예를 들어, 아래 코드 스니펫에서 querySelectorAll 메서드가 문서 내의 모든 <p> 요소 목록을 반환해야 한다고 DOM에서 명시하고 있어요.

const paragraphs = document.querySelectorAll("p");
// paragraphs[0]은 첫 번째 <p> 요소입니다.
// paragraphs[1]은 두 번째 <p> 요소입니다, 등등.
alert(paragraphs[0].nodeName);

웹 페이지를 조작하고 만드는 데 사용할 수 있는 모든 속성(properties), 메서드(methods), 이벤트(events)들은 객체로 구성되어 있어요. 예를 들어, 문서 자체를 나타내는 document 객체, HTML 테이블에 접근하기 위해 HTMLTableElement DOM 인터페이스를 구현하는 모든 table 객체 등이 전부 객체랍니다.

DOM은 함께 작동하는 여러 API를 사용해서 만들어져요. 코어(Core) DOM은 문서를 설명하는 엔티티와 그 안의 객체들을 정의하죠. 그리고 DOM에 새로운 기능과 역량을 추가하는 다른 API들에 의해 필요에 따라 확장돼요. 예를 들어, HTML DOM API는 코어 DOM에 HTML 문서를 표현하는 기능을 추가하고, SVG API는 SVG 문서를 표현하는 기능을 추가해 준답니다.

DOM 트리가 뭘까요? (What is a DOM tree?)

DOM 트리는 HTML이나 XML 문서의 내용을 노드로 표현하는 트리 구조예요. 각 HTML이나 XML 문서는 DOM 트리로 표현될 수 있어요. 예를 들어, 다음 문서를 한번 살펴볼까요.

<html lang="en">
  <head>
    <title>My Document</title>
  </head>
  <body>
    <h1>Header</h1>
    <p>Paragraph</p>
  </body>
</html>

이 문서의 DOM 트리는 다음과 같이 생겼어요.

The DOM as a tree-like representation of a document that has a root and node elements containing content

위 이미지가 앞서 본 문서의 DOM 트리와 비슷하긴 하지만, 실제 DOM 트리는 공백(whitespace)을 보존하기 때문에 완전히 똑같지는 않아요.

웹 브라우저가 HTML 문서를 파싱(parsing)할 때 DOM 트리를 만들고, 그걸 사용해서 화면에 문서를 보여주게 됩니다.

DOM과 자바스크립트 (DOM and JavaScript)

앞서 본 짧은 예제는 거의 모든 예제들이 그렇듯 자바스크립트(JavaScript)예요. 다시 말해, 자바스크립트로 작성되었지만 문서와 그 요소에 접근하기 위해 DOM을 사용한다는 뜻이죠. DOM은 프로그래밍 언어가 아니지만, DOM이 없다면 자바스크립트 언어는 웹 페이지, HTML 문서, SVG 문서나 그 구성 요소들에 대한 모델이나 개념을 가질 수 없었을 거예요. 문서 전체, head 태그, 문서 내의 테이블, 테이블 헤더, 테이블 셀 안의 텍스트 등 문서의 모든 요소는 해당 문서의 문서 객체 모델의 일부랍니다. 이것들은 모두 DOM과 자바스크립트 같은 스크립트 언어를 사용해서 접근하고 조작할 수 있어요.

DOM은 자바스크립트 언어의 일부가 아니라, 웹사이트를 만드는 데 사용되는 Web API예요. 자바스크립트는 다른 환경에서도 쓰일 수 있죠. 예를 들어 Node.js는 컴퓨터에서 자바스크립트 프로그램을 실행하지만 다른 종류의 API들을 제공하고, DOM API는 Node.js 런타임의 핵심 부분이 아니랍니다.

DOM은 특정 프로그래밍 언어에 종속되지 않도록 독립적으로 설계되었어요. 그래서 단일하고 일관된 API를 통해 문서의 구조적 표현을 다룰 수 있죠. 대부분의 웹 개발자가 자바스크립트를 통해서만 DOM을 사용하겠지만, 다음 파이썬(Python) 예제에서 볼 수 있듯이 DOM 구현체는 어떤 언어로든 만들어질 수 있어요.

# Python DOM example
import xml.dom.minidom as m
doc = m.parse(r"C:\Projects\Py\chap1.xml")
doc.nodeName # DOM property of document object
p_list = doc.getElementsByTagName("para")

웹에서 자바스크립트를 작성할 때 어떤 기술들이 쓰이는지 더 자세히 알고 싶으시다면, 자바스크립트 기술 개요를 참고해 보세요.

DOM 접근하기 (Accessing the DOM)

DOM을 사용하기 위해 뭔가 특별한 걸 할 필요는 없어요. 브라우저가 실행하는 프로그램인 스크립트 내에서 자바스크립트로 API를 바로 사용하면 된답니다.

<script> 요소 안에 인라인으로 작성하든 웹 페이지에 포함하든 스크립트를 만들게 되면, 문서 자체나 웹 페이지 안의 다양한 요소(문서의 자손 요소들)를 조작하기 위해 document나](https://developer.mozilla.org/en-US/docs/Web/API/Document)나) window 객체를 위한 API를 즉시 사용할 수 있어요. 여러분의 DOM 프로그래밍은 console.log() 함수를 사용해서 콘솔에 메시지를 띄우는 다음 예제처럼 아주 간단할 수도 있어요.

<body onload="console.log('Welcome to my home page!');"></body>

페이지의 구조(HTML로 작성됨)와 DOM 조작(자바스크립트로 작성됨)을 섞어 쓰는 건 일반적으로 권장하지 않기 때문에, 자바스크립트 부분만 따로 모아서 HTML과 분리해 두는 게 좋아요.

예를 들어, 다음 함수는 새로운 h1 요소를 만들고, 그 요소에 텍스트를 추가한 다음, 문서 트리에 추가하는 역할을 해요.

<html lang="en">
  <head> </head>
  <body>
    <script>
      // 빈 HTML 페이지에 요소 두 개를 만들어 봅니다
      const heading = document.createElement("h1");
      const headingText = document.createTextNode("Big Head!");
      heading.appendChild(headingText);
      document.body.appendChild(heading);
    </script>
  </body>
</html>

DOM 인터페이스 (DOM interfaces)

다음은 모두 DOM 명세에서 정의된 인터페이스들이에요.

이 문서는 여러분이 DOM 계층 구조를 조작할 때 사용할 수 있는 객체와 실제 대상들에 대해 다루고 있어요. 작동 방식을 이해하는 과정에서 헷갈릴 수 있는 부분들이 꽤 많죠. 예를 들어, HTML form 요소를 나타내는 객체는 HTMLFormElement 인터페이스에서 name 속성을 가져오지만, className 속성은 HTMLElement 인터페이스에서 가져와요. 하지만 두 경우 모두 여러분이 필요로 하는 속성은 바로 그 form 객체 안에 다 들어있답니다.

이처럼 객체와 그 객체가 DOM에서 구현하는 인터페이스 사이의 관계가 헷갈릴 수 있어서, 이 섹션에서는 DOM 명세의 실제 인터페이스들과 그것들이 어떻게 활용되는지 조금 설명해 보려고 해요.

인터페이스와 객체 (Interfaces and objects)

많은 객체들이 여러 개의 다양한 인터페이스를 함께 구현해요. 예를 들어 table 객체는 createCaption이나 insertRow 같은 메서드를 포함하는 특화된 HTMLTableElement 인터페이스를 구현하죠. 하지만 HTML 요소이기도 하니까, table은 DOM Element 레퍼런스 챕터에 설명된 Element 인터페이스도 구현해요. 마지막으로, DOM의 관점에서 HTML 요소는 HTML이나 XML 페이지의 객체 모델을 구성하는 노드 트리의 한 부분(노드)이기도 해서, table 객체는 Element의 기반이 되는 더 핵심적인 Node 인터페이스도 같이 구현한답니다.

다음 예제처럼 table 객체에 대한 참조(reference)를 가져오게 되면, 아마 스스로 눈치채지 못하셨더라도 이 세 가지 인터페이스를 객체 위에서 번갈아가며 사용하게 되실 거예요.

const table = document.getElementById("table");
const tableAttrs = table.attributes; // Node/Element 인터페이스
for (const attr of tableAttrs) {
  // HTMLTableElement 인터페이스: border 속성
  if (attr.nodeName.toLowerCase() === "border") {
    table.border = "1";
  }
}
// HTMLTableElement 인터페이스: summary 속성
table.summary = "note: increased border";

기본 데이터 타입 (Fundamental data types)

이 페이지에서는 다양한 객체와 타입들을 최대한 이해하기 쉽게 설명하려고 해요. 그래도 API에서 주고받는 몇 가지 주요 데이터 타입들에 대해서는 꼭 알아두시면 좋답니다.

참고:
DOM을 사용하는 대부분의 코드가 HTML 문서를 조작하는 것에 맞춰져 있다 보니, 엄밀히 말해서 모든 노드가 요소(element)는 아님에도 불구하고 DOM의 노드들을 요소(elements)라고 부르는 경우가 꽤 많아요.

다음 표에서 이런 데이터 타입들을 간단히 정리해 드릴게요.

데이터 타입 (인터페이스)설명
Document어떤 멤버가 document 타입의 객체를 반환할 때 (예를 들어, 요소의 ownerDocument 속성이 그 요소가 속한 document를 반환할 때), 이 객체는 루트 document 객체 그 자체를 말해요. DOM document 레퍼런스 챕터에서 이 document 객체에 대해 더 자세히 설명하고 있어요.
Node문서 안에 있는 모든 객체는 어떤 종류든 노드(node)에 해당해요. HTML 문서에서 객체는 요소(element) 노드일 수도 있지만, 텍스트 노드나 속성(attribute) 노드일 수도 있어요.
Elementelement 타입은 node를 기반으로 해요. DOM API의 멤버가 반환하는 요소나 element 타입의 노드를 가리키죠. 예를 들어, document.createElement() 메서드가 node에 대한 객체 참조를 반환한다고 말하는 대신, 그냥 DOM에 방금 만들어진 element를 반환한다고 표현해요. element 객체들은 DOM Element 인터페이스뿐만 아니라 더 기본적인 Node 인터페이스도 구현하는데, 이 레퍼런스에서는 두 가지를 한 번에 다루고 있어요. HTML 문서에서 요소들은 HTML DOM API의 HTMLElement 인터페이스나, 특정 종류의 요소가 가진 고유 기능을 설명하는 다른 인터페이스들을 통해 기능이 더욱 확장돼요 (예를 들면 <table> 요소를 위한 HTMLTableElement처럼요](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTableElement)처럼요)).
Attr멤버가 attribute를 반환할 때 (예를 들어 createAttribute() 메서드를 통해), 속성만을 위한 작지만 특별한 인터페이스를 노출하는 객체 참조를 의미해요. 속성도 요소와 마찬가지로 DOM의 노드이긴 하지만, 노드로서 직접 사용할 일은 거의 없으실 거예요.

개발할 때 알아두면 좋은 흔한 용어들도 있어요. 예를 들어 어떤 Attr 노드든 통틀어서 attribute라고 부르거나, DOM 노드들의 배열을 nodeList라고 부르는 게 일반적이죠. 이런 용어들은 앞으로 공식 문서 전체에서 자주 접하시게 될 거예요.

documentwindow 객체는 DOM 프로그래밍을 할 때 인터페이스를 가장 자주 사용하게 되는 객체들이에요. 아주 쉽게 말해서, window 객체는 브라우저 같은 것을 나타내고, document 객체는 문서 자체의 뿌리(root)를 나타낸다고 보시면 돼요. Element는 범용적인 Node 인터페이스를 상속받고, 이 두 인터페이스가 합쳐져서 개별 요소에서 사용하는 수많은 메서드와 속성을 여러분께 제공해 줘요. 이런 요소들은 앞서 살펴본 table 객체 예시처럼, 자신이 담고 있는 데이터의 성격에 맞춰 특정 인터페이스를 따로 가질 수도 있답니다.

더 이상 사용되지 않는 DOM 인터페이스 (Obsolete DOM interfaces)

문서 객체 모델(DOM)이 훨씬 단순하게 정돈되었어요. 이를 위해서 DOM 레벨 3 이전의 스펙에 존재하던 다음 인터페이스들이 모두 제거되었답니다. 웹 개발자들은 더 이상 이 기능들을 사용할 수 없어요.

  • DOMConfiguration
  • DOMErrorHandler
  • DOMImplementationList
  • DOMImplementationRegistry
  • DOMImplementationSource
  • DOMLocator
  • DOMObject
  • DOMSettableTokenList
  • DOMUserData
  • ElementTraversal
  • Entity
  • EntityReference
  • NameList
  • Notation
  • TypeInfo
  • UserDataHandler

HTML DOM

HTML이 포함된 문서는 Document 인터페이스를 사용해서 나타내는데, 이는 HTML 스펙에 의해 다양한 HTML 전용 기능들을 포함하도록 확장되었어요. 특히, Element 인터페이스가 한 단계 발전해서 HTMLElement 및 여러 하위 클래스들로 나뉘게 되었고, 각각은 하나의 요소(또는 밀접하게 관련된 요소들의 그룹)를 나타내게 되었답니다.

HTML DOM API는 탭이나 창, CSS 스타일과 스타일시트, 브라우저 방문 기록 등 브라우저의 다양한 기능에 접근할 수 있게 해 줘요. 이 인터페이스들에 대한 좀 더 깊이 있는 내용은 HTML DOM API 공식 문서에서 확인하실 수 있어요.

SVG DOM

마찬가지로, SVG가 포함된 문서도 Document 인터페이스를 사용해서 설명돼요. 이는 SVG 명세에 의해 다양한 SVG 전용 기능들을 포함하도록 확장되었답니다. 특히, Element 인터페이스가 한 단계 발전해서 SVGElement와](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement)와) 여러 하위 클래스들로 나뉘게 되었고, 각각은 하나의 요소(또는 밀접하게 관련된 요소들의 그룹)를 나타내요. 이 인터페이스들에 대한 더 자세한 내용은 SVG API 공식 문서에서 확인하실 수 있어요.

예제 (Examples)

텍스트 콘텐츠 설정하기 (Setting text content)

이 예제에서는 <textarea> 하나와 <button> 두 개를 포함하고 있는 <div> 요소를 사용해요. 사용자가 첫 번째 버튼을 클릭하면 <textarea> 안에 텍스트를 채우고, 두 번째 버튼을 클릭하면 텍스트를 지우게 됩니다. 여기서 사용하는 것들은 다음과 같아요.

HTML

<div class="container">
  <textarea class="story"></textarea>
  <button id="set-text" type="button">Set text content</button>
  <button id="clear-text" type="button">Clear text content</button>
</div>

CSS

.container {
  display: flex;
  gap: 0.5rem;
  flex-direction: column;
}

button {
  width: 200px;
}

JavaScript

const story = document.querySelector(".story");

const setText = document.querySelector("#set-text");
setText.addEventListener("click", () => {
  story.textContent = "It was a dark and stormy night...";
});

const clearText = document.querySelector("#clear-text");
clearText.addEventListener("click", () => {
  story.textContent = "";
});

자식 요소 추가하기 (Adding a child element)

이 예제는 <div> 하나와 <button> 두 개를 담고 있는 또 다른 <div> 요소를 사용해요. 사용자가 첫 번째 버튼을 클릭하면 새로운 요소를 만들어서 <div>의 자식 요소로 추가하고, 두 번째 버튼을 클릭하면 그 자식 요소를 제거하는 방식이죠. 여기서 사용하는 기능들은 다음과 같아요.

HTML

<div class="container">
  <div class="parent">parent</div>
  <button id="add-child" type="button">Add a child</button>
  <button id="remove-child" type="button">Remove child</button>
</div>

CSS

.container {
  display: flex;
  gap: 0.5rem;
  flex-direction: column;
}

button {
  width: 100px;
}

div.parent {
  border: 1px solid black;
  padding: 5px;
  width: 100px;
  height: 100px;
}

div.child {
  border: 1px solid red;
  margin: 10px;
  padding: 5px;
  width: 80px;
  height: 60px;
  box-sizing: border-box;
}

JavaScript

const parent = document.querySelector(".parent");

const addChild = document.querySelector("#add-child");
addChild.addEventListener("click", () => {
  // "parent" 텍스트 노드 외에 이미 자식 요소가 있다면
  // 새로 추가하지 않고 함수를 종료합니다.
  if (parent.childNodes.length > 1) {
    return;
  }
  const child = document.createElement("div");
  child.classList.add("child");
  child.textContent = "child";
  parent.appendChild(child);
});

const removeChild = document.querySelector("#remove-child");
removeChild.addEventListener("click", () => {
  const child = document.querySelector(".child");
  parent.removeChild(child);
});

트리 읽기 및 수정하기 (Reading and modifying the tree)

만약 작성자가 앞서 다뤘던 DOM 트리가 뭘까요? 섹션의 문서 헤더를 변경하고, 단락(paragraph)을 하나 대신 두 개로 늘리고 싶다고 가정해 볼게요. 다음 스크립트가 딱 그 역할을 해준답니다.

HTML

<html lang="en">
  <head>
    <title>My Document</title>
  </head>
  <body>
    <input type="button" value="Change this document." />
    <h2>Header</h2>
    <p>Paragraph</p>
  </body>
</html>

JavaScript

document.querySelector("input").addEventListener("click", () => {
  // document.getElementsByTagName("h2")는 문서 내의 <h2> 요소들의
  // NodeList를 반환하며, 첫 번째 요소의 인덱스는 0입니다:
  const header = document.getElementsByTagName("h2").item(0);

  // header의 첫 번째 자식(firstChild)은 텍스트 노드입니다:
  header.firstChild.data = "A dynamic document";

  // 이제 header는 "A dynamic document"가 됩니다.

  // 첫 번째 단락에 접근합니다
  const para = document.getElementsByTagName("p").item(0);
  para.firstChild.data = "This is the first paragraph.";

  // 두 번째 단락을 위한 새로운 텍스트 노드를 만듭니다
  const newText = document.createTextNode("This is the second paragraph.");

  // 두 번째 단락이 될 새로운 요소를 만듭니다
  const newElement = document.createElement("p");

  // 단락 안에 텍스트를 넣습니다
  newElement.appendChild(newText);

  // 이 단락을 문서의 끝(para의 부모인 body)에 추가합니다
  para.parentNode.appendChild(newElement);
});

트리 생성하기 (Creating a tree)

DOM 트리가 뭘까요?에서 보았던 트리를 100% 자바스크립트만으로 만들어낼 수도 있어요.

const root = document.createElement("html");
root.lang = "en";

const head = document.createElement("head");
const title = document.createElement("title");
title.appendChild(document.createTextNode("My Document"));
head.appendChild(title);

const body = document.createElement("body");
const header = document.createElement("h1");
header.appendChild(document.createTextNode("Header"));
const paragraph = document.createElement("p");
paragraph.appendChild(document.createTextNode("Paragraph"));
body.appendChild(header);
body.appendChild(paragraph);

root.appendChild(head);
root.appendChild(body);

이벤트 전파 (Event Propagation)

이 예제는 DOM에서 이벤트가 어떻게 발생하고 처리되는지 아주 간단하게 보여줘요. HTML 문서의 BODY가 로드될 때, TABLE의 첫 번째 행에 이벤트 리스너가 등록되죠. 이 이벤트 리스너는 stopEvent라는 함수를 실행해서 이벤트를 처리하는데, 이 함수는 테이블의 맨 아래 셀의 값을 변경하는 역할을 해요.

하지만 stopEvent 함수는 이벤트 객체의 메서드인 event.stopPropagation도](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation)도) 함께 호출해요. 이 메서드는 이벤트가 DOM 트리를 따라 더 이상 위로 전파(bubbling)되지 않도록 막아주죠. 여기서 주목할 점은 테이블 자체에도 클릭 시 메시지를 띄워야 하는 onclick 이벤트 핸들러가 있다는 거예요. 하지만 stopEvent 메서드가 전파를 멈췄기 때문에, 테이블의 데이터가 업데이트된 후에는 이벤트 단계가 사실상 종료되어 버리고 이를 확인하는 알림창(alert box)이 표시된답니다.

<table id="t-daddy">
  <tbody>
    <tr id="tbl1">
      <td id="c1">one</td>
    </tr>
    <tr>
      <td id="c2">two</td>
    </tr>
  </tbody>
</table>
#t-daddy {
  border: 1px solid red;
}

#c1 {
  background-color: pink;
}
function stopEvent(event) {
  const c2 = document.getElementById("c2");
  c2.textContent = "hello";

  // 이것은 t-daddy가 클릭 이벤트를 받지 못하도록 막아줄 겁니다.
  event.stopPropagation();
  console.log("event propagation halted.");
}

const elem = document.getElementById("tbl1");
elem.addEventListener("click", stopEvent);

document.getElementById("t-daddy").addEventListener("click", () => {
  console.log("t-daddy clicked");
});

이벤트 객체의 속성 표시하기 (Displaying event object properties)

이 예제는 DOM 메서드를 사용해서 onload event 객체의 모든 속성과 그 값들을 테이블 형태로 화면에 보여줘요. 객체의 속성들을 순회하며 값을 가져오기 위해 for...in 루프를 사용하는 유용한 기법도 함께 살펴볼 수 있답니다.

이벤트 객체의 속성들은 브라우저마다 꽤 큰 차이가 있어요. WHATWG DOM 표준(WHATWG DOM Standard)에서 표준 속성들을 나열해 놓긴 했지만, 많은 브라우저들이 이 기능들을 독자적으로 많이 확장해 두었거든요.

다음 코드를 빈 텍스트 파일에 넣고 여러 종류의 브라우저에서 실행해 보세요. 브라우저마다 속성의 개수나 이름이 달라서 깜짝 놀라실 수도 있어요. 페이지에 요소들을 좀 더 추가해 본 다음, 다양한 이벤트 핸들러에서 이 함수를 호출해 보는 것도 재미있는 공부가 될 거예요.

<h1>Properties of the DOM <span id="eventType"></span> Event Object</h1>
table {
  border-collapse: collapse;
}
thead {
  font-weight: bold;
}
td {
  padding: 2px 10px;
}

.odd {
  background-color: #efdfef;
}
.even {
  background-color: white;
}
function showEventProperties(e) {
  function addCell(row, text) {
    const cell = row.insertCell(-1);
    cell.appendChild(document.createTextNode(text));
  }

  const event = e || window.event;
  document.getElementById("eventType").textContent = event.type;

  const table = document.createElement("table");
  const thead = table.createTHead();
  let row = thead.insertRow(-1);
  const labelList = ["#", "Property", "Value"];
  const len = labelList.length;

  for (let i = 0; i < len; i++) {
    addCell(row, labelList[i]);
  }

  const tbody = document.createElement("tbody");
  table.appendChild(tbody);

  for (const p in event) {
    row = tbody.insertRow(-1);
    row.className = row.rowIndex % 2 ? "odd" : "even";
    addCell(row, row.rowIndex);
    addCell(row, p);
    addCell(row, event[p]);
  }

  document.body.appendChild(table);
}

showEventProperties(event);
profile
프론트에_가까운_풀스택_개발자

0개의 댓글