Document Object Model (DOM)/Anatomy of the DOM

김동현·2026년 4월 7일

Node 인터페이스와 하위 클래스들 (The Node interface and its subclasses)

DOM에 있는 모든 노드들은 Node 인터페이스를 구현하는 객체들로 표현돼요. 이 Node 인터페이스는 앞서 정의했던 여러 개념들을 그대로 담고 있죠.

여러분은 순수한 Node 객체를 직접 다룰 일은 거의 없을 거예요. 대신, DOM에 있는 모든 객체는 문서 내에서 추가적인 의미를 나타내는, Node를 상속받은 인터페이스 중 하나를 구현한답니다. 노드의 타입에 따라 어떤 데이터를 담을 수 있는지, 어떤 타입의 자식을 가질 수 있는지가 제한돼요. 다음 HTML 문서가 DOM에서 어떻게 표현되는지 한번 생각해 볼까요?

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Hello, world!</h1>
    <p>This is a paragraph.</p>
  </body>
</html>

이 문서는 다음과 같은 DOM 트리를 만들어 내요.

The DOM tree of the previous HTML document

이 DOM 트리의 루트(뿌리)는 문서 전체를 나타내는 Document 노드예요. 이 노드는 전역에서 document 변수로 접근할 수 있어요. 이 노드는 두 개의 중요한 자식 노드를 가지고 있답니다.

DocumentType 노드는 항상 잎(leaf) 노드예요. 반면 Element 노드는 문서 내용의 대부분이 표현되는 곳이죠. 그 아래에 있는 <head>, <body>, <p> 같은 각 요소들도 모두 Element 노드로 표현돼요. 사실 각 요소는 HTML 명세에 정의된 그 태그 이름에 맞는 Element의 특정 하위 클래스(HTMLHeadElement나](https://developer.mozilla.org/en-US/docs/Web/API/HTMLHeadElement)나) HTMLBodyElement 같은 것들)라서 그 요소의 특성을 나타내는 추가 속성이나 메서드를 가지고 있지만, 여기서는 DOM의 공통적인 동작 방식에만 집중할게요. Element 노드들은 중첩된 요소를 표현하기 위해 다른 Element 노드들을 자식으로 가질 수 있어요. 예를 들어, <head> 요소는 두 개의 <meta> 요소와 하나의 <title> 요소, 이렇게 세 개의 자식을 가지고 있죠. 게다가 요소들은 텍스트 내용을 표현하기 위해 Text 노드나 CDATASection 노드를 자식으로 가질 수도 있어요. 예를 들어 <p> 요소는 "This is a paragraph."라는 문자열을 담고 있는 단일 Text 노드를 자식으로 가져요. Text 노드와 CDATASection 노드는 항상 자식이 없는 잎(leaf) 노드랍니다.

자식을 가질 수 있는 모든 노드들(Document, DocumentFragment, Element)은](https://developer.mozilla.org/en-US/docs/Web/API/Element))은) 추가로 두 가지 타입의 자식을 허용해요. 바로 Comment와](https://developer.mozilla.org/en-US/docs/Web/API/Comment)와) ProcessingInstruction 노드예요. 이 노드들도 항상 잎 노드랍니다.

각 요소는 자식 노드를 가질 수 있을 뿐만 아니라, Attr 노드로 표현되는 속성(attribute)들도 가질 수 있어요. Attr 노드도 Node 인터페이스를 확장하긴 하지만, 메인 트리 구조에 속하지는 않아요. 왜냐하면 어떤 노드의 자식도 아니고 그 부모 노드가 null이기 때문이죠. 대신에 이 노드들은 분리된 이름있는 노드 맵(named node map)에 저장되어 있고, Element 노드의 attributes 속성을 통해 접근할 수 있답니다.

Node 인터페이스는 노드의 타입을 나타내는 nodeType 속성을 정의하고 있어요. 요약하자면, 다음과 같은 노드 타입들을 소개해 드렸네요.

노드 타입nodeType유효한 자식 노드 (CommentProcessingInstruction 제외)
DocumentNode.DOCUMENT_NODE (9)DocumentType, Element
DocumentTypeNode.DOCUMENT_TYPE_NODE (10)없음
ElementNode.ELEMENT_NODE (1)Element, Text, CDATASection
TextNode.TEXT_NODE (3)없음
CDATASectionNode.CDATA_SECTION_NODE (4)없음
CommentNode.COMMENT_NODE (8)없음
ProcessingInstructionNode.PROCESSING_INSTRUCTION_NODE (7)없음
AttrNode.ATTRIBUTE_NODE (2)없음

참고:
몇몇 노드 타입을 건너뛴 걸 눈치채셨을 텐데요. Node.ENTITY_REFERENCE_NODE (5), Node.ENTITY_NODE (6), Node.NOTATION_NODE (12) 값들은 더 이상 사용되지 않고, Node.DOCUMENT_FRAGMENT_NODE (11) 값은 DOM 트리 구성 및 업데이트하기(Building and updating the DOM tree)에서 소개될 거예요.

각 노드의 데이터 (Data of each node)

각 노드 타입은 자신이 가진 데이터를 표현하는 고유한 방법을 가지고 있어요. Node 인터페이스 자체는 데이터와 관련된 세 가지 속성을 정의하는데, 다음 표에 요약해 두었어요.

노드 타입nodeNamenodeValuetextContent
Document"#document"nullnull
DocumentType해당 name (예: "html")nullnull
Element해당 tagName (예: "HTML", "BODY")null트리 순서대로 모든 텍스트 노드 자손들을 이어 붙인 문자열
Text"#text"해당 data해당 data
CDATASection"#cdata-section"해당 data해당 data
Comment"#comment"해당 data해당 data
ProcessingInstruction해당 target해당 data해당 data
Attr해당 name해당 value해당 value

Document

Document 노드 자체는 어떤 데이터도 담고 있지 않기 때문에, nodeValuetextContent는 항상 null이에요. 그리고 nodeName은 항상 "#document"랍니다.

대신 Document는 문서가 제공된 HTTP 응답 같은 환경에서 비롯된, 문서에 대한 여러 메타데이터(metadata)를 정의하고 있어요.

DocumentType

문서 내의 DocumentType은 보통 이렇게 생겼어요.

<!doctype name PUBLIC "publicId" "systemId">

여기서 지정할 수 있는 세 가지 부분은 DocumentType 노드의 세 속성인 name, publicId, systemId에](https://developer.mozilla.org/en-US/docs/Web/API/DocumentType/systemId)에) 대응돼요. HTML 문서의 경우, doctype은 항상 <!doctype html>이기 때문에 name"html"이 되고, publicIdsystemId는 모두 빈 문자열이 된답니다.

Element

문서 안의 Element는 이렇게 생겼죠.

<p class="note" id="intro">This is a paragraph.</p>

여기서는 태그 내용 말고도 지정할 수 있는 게 두 가지 있어요. 바로 태그 이름(tag name)과 속성(attributes)이죠. 태그 이름은 Element 노드의 tagName 속성에 해당하고, 이 경우엔 "P"예요 (HTML 요소의 경우 항상 대문자로 표시된다는 점을 기억하세요). 요소의 속성들은 Element 노드의 attributes 속성 안에 저장된 Attr 노드들에 해당해요. 속성에 대해서는 요소와 그 속성들(Element and its attributes) 섹션에서 더 자세히 다루도록 할게요.

Element 노드 자체는 데이터를 담지 않으므로 nodeValue는 항상 null이에요. textContent는 트리 순서대로 모든 자손 텍스트 노드들을 하나로 합친 건데, 이 경우엔 "This is a paragraph."가 되겠죠. 만약 요소가 다음과 같다면:

<div>Hello, <span>world</span>!</div>

textContent는 텍스트 노드 "Hello, ", <span> 요소 안의 텍스트 노드 "world", 그리고 텍스트 노드 "!"를 합친 "Hello, world!"가 될 거예요.

CharacterData

Text, CDATASection, Comment, 그리고 ProcessingInstruction은](https://developer.mozilla.org/en-US/docs/Web/API/ProcessingInstruction)은) 모두 Node의 하위 클래스인 CharacterData 인터페이스를 상속받아요. 이 CharacterData 인터페이스는 노드의 텍스트 내용을 담고 있는 딱 하나의 속성, data를](https://developer.mozilla.org/en-US/docs/Web/API/CharacterData/data)를) 정의하고 있죠. 이 data 속성은 이런 노드들의 nodeValuetextContent 속성을 구현하는 데도 똑같이 사용된답니다.

TextCDATASection의 경우, data 속성은 노드의 텍스트 내용을 그대로 담아요. 다음 문서에서 한번 확인해 볼까요 (HTML은 CDATA 섹션을 허용하지 않아서 SVG 문서를 예로 들었어요).

<text>Some text</text>
<style><![CDATA[h1 { color: red; }]]></style>

<text> 요소 안의 텍스트 노드는 "Some text"data로 가지고 있고, <style> 요소 안의 CDATA 섹션은 "h1 { color: red; }"data로 가지게 돼요.

Comment의 경우, data 속성은 `` 직전에 끝나는 주석의 내용을 담아요. 예를 들어 아래 문서에서:

주석 노드의 data" This is a comment "가 된답니다.

ProcessingInstruction의 경우, data 속성은 타겟(target) 이후부터 시작해서 ?> 직전에 끝나는 처리 명령의 내용을 담게 돼요. 다음 문서가 그 예시예요.

<?xml-stylesheet type="text/xsl" href="style.xsl"?>

이 처리 명령 노드는 'type="text/xsl" href="style.xsl"'data로 갖고, "xml-stylesheet"target으로](https://developer.mozilla.org/en-US/docs/Web/API/ProcessingInstruction/target)으로) 가지게 됩니다.

여기에 더해서, CharacterData 인터페이스는 data 문자열의 길이를 반환하는 length 속성과, data의 일부 문자열을 반환하는 substringData() 메서드도 제공하고 있어요.

Attr

다음과 같은 요소가 있을 때:

<p class="note" id="intro">This is a paragraph.</p>

<p> 요소는 두 개의 Attr 노드로 표현되는 두 가지 속성(attribute)을 가지고 있어요. 각 속성은 이름과 값으로 이루어져 있는데, 이는 각각 name과](https://developer.mozilla.org/en-US/docs/Web/API/Attr/name)과) value 속성에 해당해요. 첫 번째 속성의 name"class"이고 value"note"이며, 두 번째 속성의 name"id"이고 value"intro"가 된답니다.

요소와 그 속성들 (Element and its attributes)

앞서 말씀드린 대로, Element 노드의 속성(attribute)들은 Attr 노드들로 표현되고, 이는 Element 노드의 attributes 속성을 통해 접근할 수 있는 분리된 이름있는 노드 맵에 저장돼요. 이 NamedNodeMap 인터페이스는 세 가지 핵심 속성을 제공해요.

  • length: 속성의 총개수를 반환해요.
  • item() 메서드: 특정 인덱스에 있는 Attr를 가져올 수 있어요.
  • getNamedItem() 메서드: 주어진 이름과 일치하는 Attr를 찾아줘요.

Element 인터페이스 자체에도 이름있는 노드 맵을 굳이 거치지 않고 속성들을 바로 다룰 수 있는 편리한 메서드들이 마련되어 있답니다.

참고로 Attr 노드의 ownerElement 속성을 사용하면 그 속성이 어떤 요소에 붙어있는지도 알아낼 수 있어요.

또한 idclass라는 두 가지 특별한 속성이 있는데, 얘네들은 Element 인터페이스에 자신만의 속성인 id와](https://developer.mozilla.org/en-US/docs/Web/API/Element/id)와) className을](https://developer.mozilla.org/en-US/docs/Web/API/Element/className)을) 따로 가지고 있어서 해당 속성의 값을 그대로 반영(reflect)한답니다. 더불어 classList 속성을 사용하면 class 속성에 들어있는 클래스들의 목록을 DOMTokenList 형태로 아주 편하게 다룰 수 있어요.

요소 트리 다루기 (Working with the element tree)

Element 노드들이 문서 구조의 뼈대를 이루고 있기 때문에, TextComment 같은 다른 노드들은 건너뛰고 오직 요소(element) 노드들만 쏙쏙 골라서 트리를 탐색할 수도 있어요.

노드 비교하기 (Comparing nodes)

노드끼리 비교할 때 아주 요긴하게 쓰이는 세 가지 메서드가 있어요. 바로 isEqualNode(), isSameNode(), compareDocumentPosition() 랍니다.

isSameNode() 메서드는 과거의 흔적인데(legacy), 요즘은 일치 연산자(strict equality operator) (===)와 똑같이 동작해서 두 노드가 정말 완전히 동일한 객체일 때만 true를 반환해요.

isEqualNode() 메서드는 두 노드의 구조를 꼼꼼하게 따져봐요. 두 노드의 타입이 같고, 가진 데이터가 같으며, 그들의 자식 노드들까지 순서대로 모두 똑같다면 같다고 판단하죠. 각 노드의 데이터(Data of each node) 섹션에서 노드 타입별로 어떤 데이터가 중요한지 이미 정리해 두었어요.

  • Document는 데이터가 없으니 자식 노드들만 서로 똑같은지 비교하면 돼요.
  • DocumentTypename, publicId, systemId 속성값들이 일치하는지 비교해요.
  • ElementtagName(좀 더 정확히는 namespaceURI, prefix, localName인데, 이건 XML 네임스페이스(XML namespaces) 가이드에서 다룰 예정이에요)과 가지고 있는 속성(attribute)들을 비교한답니다.
  • Attrname(역시나 정확히는 namespaceURI, prefix, localName)과 value 속성값이 같은지 비교해요.
  • 모든 CharacterData 노드들(Text, CDATASection, Comment, ProcessingInstruction)은 data 속성이 일치하는지 봐요. ProcessingInstruction은 추가로 target 속성까지도 비교하죠.

a.compareDocumentPosition(b) 메서드는 트리 순서를 기준으로 두 노드를 비교해요. 비교 결과를 비트마스크(bitmask) 형태로 반환해서 두 노드의 상대적인 위치를 알려준답니다. 반환될 수 있는 경우는 다음과 같아요.

  • ab가 똑같은 노드라면 0을 반환해요.
  • 두 노드가 모두 같은 요소 노드(element node)의 속성(attribute)이라면, 속성 목록에서 ab보다 먼저 오면 Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC (34)를, a가 뒤에 오면 Node.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC (36)을 반환해요. 만약 둘 중 하나라도 속성 노드라면, 비교할 때 그 속성이 붙어있는 주체 요소(owner element)를 기준으로 삼게 돼요.
  • 두 노드가 같은 루트 노드를 공유하지 않는다면(아예 떨어진 트리라면), Node.DOCUMENT_POSITION_DISCONNECTED | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_PRECEDING (35)나 Node.DOCUMENT_POSITION_DISCONNECTED | Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_FOLLOWING (37) 중 하나를 반환하는데, 이 둘 중 어떤 값이 나올지는 브라우저의 구현 방식에 따라 다르답니다.
  • ab의 조상이라면(ba의 속성인 경우도 포함해서요), Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING (10)을 반환해요.
  • ab의 자손이라면(ab의 속성인 경우도 포함해서요), Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_FOLLOWING (20)을 반환하죠.
  • a가 트리 순서상 b보다 앞선다면, Node.DOCUMENT_POSITION_PRECEDING (2)를 반환해요.
  • a가 트리 순서상 b보다 뒤에 있다면, Node.DOCUMENT_POSITION_FOLLOWING (4)를 반환한답니다.

이렇게 비트마스크 값을 사용하기 때문에, 비트 단위의 AND 연산(&)을 써서 특정 관계가 맞는지 아주 쉽게 확인할 수 있어요. 예를 들어 ab보다 앞서는지 확인하고 싶다면 이렇게 하시면 돼요.

if (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING) {
  // a가 b보다 먼저 나옵니다
}

이 코드는 ab가 같은 요소의 속성이면서 a가 먼저 나올 때, ab의 조상일 때, 그리고 트리 구조에서 순서상 ab보다 앞설 때의 모든 상황을 완벽하게 포착해 낸답니다.

요약 (Summary)

지금까지 살펴본 모든 기능들을 한번 깔끔하게 정리해 볼게요. 내용이 꽤 많아 보이지만, 전부 다 각자의 상황에서 정말 유용하게 쓰이는 것들이랍니다.

profile
프론트에_가까운_풀스택_개발자

0개의 댓글