DOM에 있는 모든 노드들은 Node 인터페이스를 구현하는 객체들로 표현돼요. 이 Node 인터페이스는 앞서 정의했던 여러 개념들을 그대로 담고 있죠.
parentNode 속성은 부모 노드를 반환해요. 부모가 없다면 null을 반환하죠.childNodes 속성은 자식 노드들이 담긴 NodeList를](https://developer.mozilla.org/en-US/docs/Web/API/NodeList)를) 반환해요. firstChild와](https://developer.mozilla.org/en-US/docs/Web/API/Node/firstChild)와) lastChild 속성은 각각 이 목록의 첫 번째와 마지막 요소를 반환하고, 자식이 없다면 null을 반환한답니다.getRootNode() 메서드는 부모 링크를 계속 따라 올라가서 해당 노드가 포함된 트리의 루트(root)를 반환해요.hasChildNodes() 메서드는 자식 노드가 하나라도 있으면 (즉, 잎(leaf) 노드가 아니면) true를 반환해요.previousSibling과](https://developer.mozilla.org/en-US/docs/Web/API/Node/previousSibling)과) nextSibling 속성은 각각 이전 형제 노드와 다음 형제 노드를 반환해요. 그런 형제가 없다면 null을 반환하죠.contains() 메서드는 주어진 노드가 해당 노드의 자손(descendant)이면 true를 반환해요.compareDocumentPosition() 메서드는 트리 순서를 기준으로 두 노드를 비교해요. 이 메서드에 대한 더 자세한 내용은 노드 비교하기(Comparing nodes) 섹션에서 다룰게요.여러분은 순수한 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 트리를 만들어 내요.
이 DOM 트리의 루트(뿌리)는 문서 전체를 나타내는 Document 노드예요. 이 노드는 전역에서 document 변수로 접근할 수 있어요. 이 노드는 두 개의 중요한 자식 노드를 가지고 있답니다.
DocumentType 노드는 doctype 선언을 나타내요. 이 예제 문서에는 선언이 포함되어 있죠. 이 노드는 Document 노드의 doctype 속성을 통해서도 접근할 수 있어요.Element 노드는 루트 요소를 나타내요. HTML 문서(이 예제처럼요)의 경우, 보통 HTMLHtmlElement가](https://developer.mozilla.org/en-US/docs/Web/API/HTMLHtmlElement)가) 됩니다. SVG 문서라면 일반적으로 SVGSVGElement가](https://developer.mozilla.org/en-US/docs/Web/API/SVGSVGElement)가) 되고요. 이 노드 역시 Document 노드의 documentElement 속성을 통해 접근할 수 있어요.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 값 | 유효한 자식 노드 (Comment와 ProcessingInstruction 제외) |
|---|---|---|
Document | Node.DOCUMENT_NODE (9) | DocumentType, Element |
DocumentType | Node.DOCUMENT_TYPE_NODE (10) | 없음 |
Element | Node.ELEMENT_NODE (1) | Element, Text, CDATASection |
Text | Node.TEXT_NODE (3) | 없음 |
CDATASection | Node.CDATA_SECTION_NODE (4) | 없음 |
Comment | Node.COMMENT_NODE (8) | 없음 |
ProcessingInstruction | Node.PROCESSING_INSTRUCTION_NODE (7) | 없음 |
Attr | Node.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)에서 소개될 거예요.
각 노드 타입은 자신이 가진 데이터를 표현하는 고유한 방법을 가지고 있어요. Node 인터페이스 자체는 데이터와 관련된 세 가지 속성을 정의하는데, 다음 표에 요약해 두었어요.
| 노드 타입 | nodeName | nodeValue | textContent |
|---|---|---|---|
Document | "#document" | null | null |
DocumentType | 해당 name (예: "html") | null | null |
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 노드 자체는 어떤 데이터도 담고 있지 않기 때문에, nodeValue와 textContent는 항상 null이에요. 그리고 nodeName은 항상 "#document"랍니다.
대신 Document는 문서가 제공된 HTTP 응답 같은 환경에서 비롯된, 문서에 대한 여러 메타데이터(metadata)를 정의하고 있어요.
URL과](https://developer.mozilla.org/en-US/docs/Web/API/Document/URL)과) documentURI 속성은 문서의 URL을 반환해요.characterSet 속성은 문서에서 사용된 문자 인코딩(예: "UTF-8")을 반환해요.compatMode 속성은 문서의 렌더링 모드를 반환하는데, "CSS1Compat" (표준 모드) 이거나 "BackCompat" (호환성/quirks 모드) 중 하나예요.contentType 속성은 문서의 미디어 타입을 반환해요 (HTML 문서라면 "text/html" 같이요).문서 내의 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"이 되고, publicId와 systemId는 모두 빈 문자열이 된답니다.
문서 안의 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!"가 될 거예요.
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 속성은 이런 노드들의 nodeValue와 textContent 속성을 구현하는 데도 똑같이 사용된답니다.
Text와 CDATASection의 경우, 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() 메서드도 제공하고 있어요.
다음과 같은 요소가 있을 때:
<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 노드의 속성(attribute)들은 Attr 노드들로 표현되고, 이는 Element 노드의 attributes 속성을 통해 접근할 수 있는 분리된 이름있는 노드 맵에 저장돼요. 이 NamedNodeMap 인터페이스는 세 가지 핵심 속성을 제공해요.
length: 속성의 총개수를 반환해요.item() 메서드: 특정 인덱스에 있는 Attr를 가져올 수 있어요.getNamedItem() 메서드: 주어진 이름과 일치하는 Attr를 찾아줘요.Element 인터페이스 자체에도 이름있는 노드 맵을 굳이 거치지 않고 속성들을 바로 다룰 수 있는 편리한 메서드들이 마련되어 있답니다.
element.getAttribute(name): 속성이 존재한다면 element.attributes.getNamedItem(name).value와 똑같이 동작해요.element.getAttributeNode(name): element.attributes.getNamedItem(name)과 똑같이 동작해요.element.hasAttribute(name): element.attributes.getNamedItem(name) !== null과 똑같이 동작해서 속성 유무를 확인해 주죠.element.getAttributeNames(): 모든 속성의 이름들을 배열로 묶어서 반환해요.element.hasAttributes(): element.attributes.length > 0과 똑같이 동작해서 속성이 하나라도 있는지 알려준답니다.참고로 Attr 노드의 ownerElement 속성을 사용하면 그 속성이 어떤 요소에 붙어있는지도 알아낼 수 있어요.
또한 id와 class라는 두 가지 특별한 속성이 있는데, 얘네들은 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 형태로 아주 편하게 다룰 수 있어요.
Element 노드들이 문서 구조의 뼈대를 이루고 있기 때문에, Text나 Comment 같은 다른 노드들은 건너뛰고 오직 요소(element) 노드들만 쏙쏙 골라서 트리를 탐색할 수도 있어요.
parentElement 속성은 부모가 Element일 때만 해당 부모 노드를 반환해요. 만약 부모가 Document처럼 Element가 아니라면 null을 반환하죠. 부모 노드의 타입에 상관없이 무조건 부모를 반환하는 parentNode와는](https://developer.mozilla.org/en-US/docs/Web/API/Node/parentNode)와는) 약간 차이가 있어요.Document, DocumentFragment, Element의 경우, children 속성은 자식들 중에서 오직 Element 노드들만 모아서 HTMLCollection으로](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection)으로) 반환해요. 자식 노드의 타입에 상관없이 전부 다 반환하는 childNodes와](https://developer.mozilla.org/en-US/docs/Web/API/Node/childNodes)와) 비교되는 부분이죠. firstElementChild와](https://developer.mozilla.org/en-US/docs/Web/API/Element/firstElementChild)와) lastElementChild 속성은 이 children 컬렉션의 첫 번째와 마지막 요소를 가져오고, 요소 자식이 없다면 null을 반환한답니다. childElementCount 속성은 자식 요소의 총개수를 세어주고요.Element와 CharacterData의 경우, previousElementSibling과](https://developer.mozilla.org/en-US/docs/Web/API/Element/previousElementSibling)과) nextElementSibling 속성은 이전이나 다음 형제 중에서 Element인 노드만 찾아줘요. 그런 형제 요소가 없다면 null을 반환하죠. 타입에 관계없이 바로 옆의 형제를 무조건 반환하는 previousSibling이나](https://developer.mozilla.org/en-US/docs/Web/API/Node/previousSibling)이나) nextSibling과는](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)과는) 역시 차이가 있습니다.노드끼리 비교할 때 아주 요긴하게 쓰이는 세 가지 메서드가 있어요. 바로 isEqualNode(), isSameNode(), compareDocumentPosition() 랍니다.
isSameNode() 메서드는 과거의 흔적인데(legacy), 요즘은 일치 연산자(strict equality operator) (===)와 똑같이 동작해서 두 노드가 정말 완전히 동일한 객체일 때만 true를 반환해요.
isEqualNode() 메서드는 두 노드의 구조를 꼼꼼하게 따져봐요. 두 노드의 타입이 같고, 가진 데이터가 같으며, 그들의 자식 노드들까지 순서대로 모두 똑같다면 같다고 판단하죠. 각 노드의 데이터(Data of each node) 섹션에서 노드 타입별로 어떤 데이터가 중요한지 이미 정리해 두었어요.
Document는 데이터가 없으니 자식 노드들만 서로 똑같은지 비교하면 돼요.DocumentType은 name, publicId, systemId 속성값들이 일치하는지 비교해요.Element는 tagName(좀 더 정확히는 namespaceURI, prefix, localName인데, 이건 XML 네임스페이스(XML namespaces) 가이드에서 다룰 예정이에요)과 가지고 있는 속성(attribute)들을 비교한답니다.Attr는 name(역시나 정확히는 namespaceURI, prefix, localName)과 value 속성값이 같은지 비교해요.CharacterData 노드들(Text, CDATASection, Comment, ProcessingInstruction)은 data 속성이 일치하는지 봐요. ProcessingInstruction은 추가로 target 속성까지도 비교하죠.a.compareDocumentPosition(b) 메서드는 트리 순서를 기준으로 두 노드를 비교해요. 비교 결과를 비트마스크(bitmask) 형태로 반환해서 두 노드의 상대적인 위치를 알려준답니다. 반환될 수 있는 경우는 다음과 같아요.
a와 b가 똑같은 노드라면 0을 반환해요.a가 b보다 먼저 오면 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) 중 하나를 반환하는데, 이 둘 중 어떤 값이 나올지는 브라우저의 구현 방식에 따라 다르답니다.a가 b의 조상이라면(b가 a의 속성인 경우도 포함해서요), Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING (10)을 반환해요.a가 b의 자손이라면(a가 b의 속성인 경우도 포함해서요), 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 연산(&)을 써서 특정 관계가 맞는지 아주 쉽게 확인할 수 있어요. 예를 들어 a가 b보다 앞서는지 확인하고 싶다면 이렇게 하시면 돼요.
if (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING) {
// a가 b보다 먼저 나옵니다
}
이 코드는 a와 b가 같은 요소의 속성이면서 a가 먼저 나올 때, a가 b의 조상일 때, 그리고 트리 구조에서 순서상 a가 b보다 앞설 때의 모든 상황을 완벽하게 포착해 낸답니다.
지금까지 살펴본 모든 기능들을 한번 깔끔하게 정리해 볼게요. 내용이 꽤 많아 보이지만, 전부 다 각자의 상황에서 정말 유용하게 쓰이는 것들이랍니다.
Node 인터페이스를 구현하고 있어요.parentNode, childNodes, firstChild/](https://developer.mozilla.org/en-US/docs/Web/API/Node/firstChild)/)lastChild, hasChildNodes(), getRootNode(), previousSibling/](https://developer.mozilla.org/en-US/docs/Web/API/Node/previousSibling)/)nextSibling을](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)을) 사용하세요.parentElement, children, firstElementChild/](https://developer.mozilla.org/en-US/docs/Web/API/Element/firstElementChild)/)lastElementChild, childElementCount, previousElementSibling/](https://developer.mozilla.org/en-US/docs/Web/API/Element/previousElementSibling)/)nextElementSibling을](https://developer.mozilla.org/en-US/docs/Web/API/Element/nextElementSibling)을) 사용하시면 훨씬 편해요.nodeType 속성으로 노드의 정체를 확인할 수 있고, 노드가 품고 있는 데이터는 nodeName, nodeValue, textContent 속성으로 열어볼 수 있어요.Document 노드와 그 노드의 가장 중요한 두 자식은 doctype과](https://developer.mozilla.org/en-US/docs/Web/API/Document/doctype)과) documentElement 랍니다.DocumentType 노드에는 세 가지 핵심 속성이 있어요: name, publicId, systemId.Element 노드의 특징을 나타내는 주요 속성으로는: tagName, attributes가](https://developer.mozilla.org/en-US/docs/Web/API/Element/attributes)가) 있어요.Attr 노드는: name과](https://developer.mozilla.org/en-US/docs/Web/API/Attr/name)과) value 속성을 가지고 있죠.CharacterData 인터페이스는 자신의 내용을 담는 딱 하나의 속성, data를](https://developer.mozilla.org/en-US/docs/Web/API/CharacterData/data)를) 가지고 있어요.CharacterData를](https://developer.mozilla.org/en-US/docs/Web/API/CharacterData)를) 물려받은 네 가지 하위 클래스가 있어요: Text, CDATASection, Comment, 그리고 ProcessingInstruction이죠](https://developer.mozilla.org/en-US/docs/Web/API/ProcessingInstruction)이죠). 참고로 ProcessingInstruction은 여기에 더해 target이라는](https://developer.mozilla.org/en-US/docs/Web/API/ProcessingInstruction/target)이라는) 속성도 추가로 가지고 있답니다.id, className, 그리고 클래스 조작에 특화된 classList 속성들을 잘 활용해 보세요.isEqualNode(), 완전히 똑같은 객체인지 보는 isSameNode(), 그리고 트리 안에서 누가 먼저 오는지 위치를 따져보는 compareDocumentPosition().