DOM

문서 객체 모델은 HTML, XML문서에 대한 API입니다.
문서의 노드를 계층 구조 트리로 표현하고 있습니다.
개발자는 이를 추가, 제거, 수정 할 수 있습니다.

노드의 계층 구조

아래 HTML을 봅시다.

<html>
    <head>
        <title>Sample Page</title>
    </head>
    <body>
        <p>Hello World!</p>
    </body>
</html>

위 HTML을 계층 구조로 보면 아래와 같습니다

문서 노드의 자식은 html 하나 뿐인데 이를 문서 요소라고 합니다.
문서 하나에 문서 요소하나만 있을 수 있습니다.
XML은 미리 지정된 문서 요소가 없으며 어떤 요소든 문서 요소가 될 수 있습니다.

각 마크업은 트리에서 노드로 표현되며 HTML 요소들은 요소 노드로, 속성은 속성 노드로, 문서 타입은 문서 타입 노드로, 주석은 주석 노드로 표현됩니다.
이렇게 총 12가지 노드 타입이 있으며 이에 대해서 알아보겠습니다.

Node 타입

DOM 레벨 1에는 Node라는 인터페이스가 있습니다.
모든 노드에는 타입을 나타내는 nodeType 프로퍼티가 있으며 노드 타입은 다음 12가지 숫자형 상수중 하나입니다.

Node.ELEMENT_NODE : 1
Node.ATTRIBUTE_NODE : 2
Node.TEXT_NODE : 3
Node.CDATA_SECTION_NODE : 4
Node.ENTITY_REFERENCE_NODE : 5
Node.ENTITY_NODE : 6
Node.PROCESSING_INSTRUCTION_NODE : 7
Node.COMMENT_NODE : 8
Node.DOCUMENT_NODE : 9
Node.DOCUMENT_TYPE_NODE : 10
Node.DOCUMENT_FRAGMENT_NODE : 11
Node.NOTATION_NODE : 12

하지만 nodeType을 비교할 때는 직접 숫자로 비교해주는 것이 좋습니다. IE9이하 버전에서는 위 상수를 지원하지 않기 때문입니다.
예제를 보면 아래와 같습니다.

if (someNode.nodeType = 1){
    console.log("node is an element");
}

웹 브라우저는 모든 노드 타입을 지원하지 않습니다.
개발시 가장 자주 다루게 될 노드는 요소 노드와 텍스트 노드 입니다.

nodeName, nodeValue

두 프로퍼티는 해당 노드의 정보를 제공합니다.

if(someNode.nodeType == 1){
    value = someNode.nodeName; //요소의 태그 이름
}

nodeName은 항상 요소의 태그 이름이며, nodeValue는 항상 null입니다.

노드 사이의 관계

모든 노드는 다른 노드와 관계가 있습니다.
HTML <body><html>의 자식입니다. <html><body>의 부모입니다. 위에서 트리 구조 그림을 보면 쉽게 이해될 것입니다.
<head>, <body>는 형제 요소입니다. <html>을 부모로 공유하고 있기 때문입니다.

각 노드에는 childNodes 프로퍼티가 있는데 이 프로퍼티에는 NodeList가 저장됩니다.
NodeList객체는 문서가 바뀌면 살아있는 것 처럼 자동으로 반영됩니다.

NodeList는 대괄호 접근, item()메서드 접근이 가능합니다.

var firstChild = someNode.childNodes[0];
var secondChild = someNode.childNodes.item(1);
var count = someNode.childNodes.length;

어느걸 써도 좋습니다.

DOM이 저장하고 있는 관계(Node)들

부모 노드 : parentNode
자식 노드 : childNodes, 없으면 null
첫 번째 자식 노드 : firstChild, 없으면 null
마지막 자식 노드 : lastChild 없으면 null
이전 형제 노드 : previousSibling, 없으면 null
다음 형제 노드 : nextSibling, 없으면 null

이렇게 모든 노드의 관계 포인터가 연결되어있기 때문에 어느 노드든 도달할 수 있습니다.
hasChildNodes() 메서드도 존재하며 ChildNodes.length호출보다 유용합니다.
모든 노드는 공통적으로 ownerDocument프로퍼티를 가지고 있습니다.
전체 문서를 표현하는 문서 노드에 대한 포인터 입니다.
이 프로퍼티를 이용하면 노드 계층 구조를 따라 위로 거슬로 올라갈 필요가 없어집니다.

노드 조작

노드 사이의 간계 포인터는 모드 읽기전용이기 때문에 메서드를 사용해야서 수정해야 합니다.

  • appendChild() : ChildNodes목록에 노드를 추가할 수 있습니다.
  • insertBefore() : 특정 위치에 삽일을 할 수 있습니다.
  • replaceChild() : 기존 노드 교체
  • removeChild() : 노드 제거
someNode.appendChild(newNode); //마지막 자식으로 삽입
someNode.insertBefore(newNode, null); //마지막 자식으로 삽입
someNode.insertBefore(newNode, someNode.firstChild); //첫 자식으로 삽입
someNode.insertBefore(newNode, someNode.lastChild); //마지막 자식 앞에 삽입

someNode.replaceChild(newNode, someNode.firstChild); //첫 번째 자식 교체someNode.replaceChild(newNode, someNode.lastChild); //마지막 자식 교체

someNode.removeChild(someNode.firstChild); //첫 번째 자식 제거
someNode.removeChild(someNode.lastChild); //마지막 자식 제거

위 메서드들은 특정 노드의 자식에서만 동작하므로 부모 노드를 정확히 알아야합니다.
자식을 가질 수 없는 노드타입에게 이들 메서드를 호출 시 에러가 발생합니다.

기타 메서드

  • cloneNode() : 자신을 호출한 노드의 복제본 생성, 매개변수가 true이면 전체 복제, false이면 해당 노드 하나 복제
  • normalize() : 빈텍스트 노드를 찾으면 제거, 텍스트 노드끼리 형제일 경우 노드를 하나로 합치는 기능 수행

Document 타입

HTML페이지, XML기반 문서를 표현하며 document객체를 통한 HTMLDocument 인스턴스입니다.
페이지에 대한 정보를 얻고 구조 및 외관을 조작합니다.

  • document.documentElement : <html>에 대한 참조를 얻습니다.
  • document.body : <body>에 대한 참조를 얻습니다.
  • document.doctype : <!doctype>에 대한 정보를 얻습니다. (하지만 브라우저마다 달리 지원해서 유용하게 사용하기 어렵습니다.)

문서정보

Document객체에 존재하지 않는 프로퍼티를 여럿 가지고 있는데 그에 대해서 알아봅시다.
title, domain의 경우 조회는 물론 설정도 가능합니다.

  • document.title : <title>요소 텍스트가 들어 있습니다.
  • document.URL : 완전한 URL
  • document.domain : domain이름
  • document.referrer : 레퍼러

domain은 다소 제한적입니다.
현재 URL에 나타나지 않은 도메인으로 바꿀 수 없습니다.
크로스도메인 보안 제한 때문입니다.
하지만 서브도메인을 사용할 경우 서브도메인에서 가져온 프레임이나 아이프레임이 있을 때 유용하게 사용 할 수 있습니다.
각 페이지의 domain을 같은 값으로 설정하면 다른 페이지의 자바스크립트 객체에 접근 가능하기 때문입니다.

요소 위치

DOM관련해서 가장 자주 사용하는 일은 특정 요소나 요소 그룹에 대한 참조를 가져오는 일입니다.

  • getElementById() : id를 매개변수로 받아서 요소를 찾습니다.
  • getElementsByTagName() : 태그이름을 매개변수로 받아서 요소를 찾습니다.
  • getElementsByName() : name속성 값이 주어진 문자열에 일치하는 요소를 찾습니다.
    <div id="myName" name="all-name">Kendrick</div>
    <div name="all-name">Dan</div>
    
    var div = document.getElementById("myName");
    var div2 = document.getElementsByTagName("div");
    var div3 = document.getElementsByName("all-name");
    
    getElementById는 동일한게 2개이상 존재한다면 가장 처음꺼반 반환합니다.
    getElementsByTagName는 반환값을 NodeList로 받기 때문에 동일한 태그네임이 여러개라도 NodeList로 전부 받습니다.

즉 위에서와 같이 NodeList[0], NodeList.item(0) 과 같이 접근할 수 있습니다.

getElementsByName은 매개변수로 받은 값과 같은 전체 name속성을 NodeList로 받습니다.

특별한 컬렉션

특별한 컬렉션이 몇가지 있으며 이에 대해서 설명합니다.
모드 HTML Collection 객체이며 문서에 공통된 요소들에 빠르게 접근하도록 해줍니다.

  • document.anchors : name속성이 있는 <a>요소를 모두 가져옵니다.
  • document.applets : <applet>요소를 모두 가져옵니다. (현재 폐기됨)
  • document.forms : <form>요소를 모두 가져옵니다.
  • document.images : <img>요소를 모두 가져옵니다.
  • document.links : href속성이 있는 <a>요소를 모두 가져옵니다.

Element 타입

Element타입은 XML/HTML요소를 표현하며 이를 통해 태그 이름이나 자식, 속성 등의 정보에 접근 가능합니다.

HTML요소

  • id : 요소의 고유한 식별자
  • title : 여소에 대한 추가 정보
  • lang : 요소 컨텐츠 언어코드 (거의 쓰이지 않음)
  • dir : 언어의 표기 방향 (거의 쓰이지 않음)
  • className : CSS클래스인 css의 속성을 나타냄

위 프로퍼티는 속성값을 읽는 용도로 사용합니다.

속성 얻기

위처럼 element의 프로퍼티를 사용하지 않고 아래와 같은 방법으로 속성에 접근할 수 있습니다.
이 방법을 사용하면 사용자가 임의로 추가한 속성도 접근이 가능합니다.

var div = documentgetElementById("myName");
console.log(div.getAttribute("name")); //all-name

위에는 name속성뿐이라 name만 썼지만 속성이름을 넣어주면 현재 element에 존재하는 속성에 접근 가능합니다.

속성 설정

getAttribute의 형제 메서드인 setAttribute()를 사용하여 속성을 설정해줄 수 있습니다.

div.setAttribute("title", "title test!");
div.setAttribute("data-name", "temp data"); //사용자 임의의 속성도 추가 가능합니다.

위에서 가져온 myName 요소에 title속성을 추가 해 줬습니다.

속성 제거

div.removeAttribute("data-name"); // 위에서 추가한 사용자 임의속성 제거

위 메서드를 이용해서 간단하게 제거 가능합니다.

attributes 프로퍼티

Element타입이 가지고 있는 attributes프로퍼티는 속성을 NodeList처럼 가지고 있습니다.

  • getNamedItem(name) : nodeName 프로퍼티가 name인 노드 반환
  • removeNamedItem(name) : nodeName 프로퍼티가 name인 노드를 목록에서 제거
  • setNamedItem(node) : node를 목록에 추가, nodeName프로퍼에 따라 색인
  • item(pos) : 인덱스가 pos인 노드 반환

주의할 점은 브라우저에 따라 attributes객체 속성반환하는 순서가 다릅니다.
IE7이하 버전은 명시되지 않은 속성도 반환합니다.

요소 생성

createElement()를 통하여 요소를 생성할 수 있습니다.

var div = document.createElement('<div id="myNewDiv" class="box" </div>');

document.body.appendChild(div); //body의 childNode에 div추가

Text타입

이 노드는 평범한 텍스트 입니다.

  • appendDat(text) : 노드 마지막에 text를 추가
  • deleteData(offset, count) : offset부터 count만큼 삭제
  • insertData(offset, text) : offset위치에 text삽입
  • replaceData(offset, count, text) : offset부터 offset + count까지의 텍스트를 text로 교체
  • splitText(offset) : offset위치를 기준으로 텍스트 노드를 둘로 나눔
  • substringData(offset, count) : offset ~ offset + count 위치의 텍스트를 꺼냄
  • length : 글자개수 반환

텍스트 노드가 있는지 없는지는 아래와 같이 판단합니다.

<div></div> //콘텐츠가 없음, 즉 텍스트 노드도 없음

<div> </div> //공백 콘텐츠가 있음, 즉 텍스트 노드가 하나 있음

<div>Hello WOrld!</div> //콘텐츠가 있으므로 텍스트 노드가 하나 있음

텍스트 노드 생성

  • createElement(text) : 엘리먼트 내에 텍스트 노드를 생성합니다.

Comment 타입

이는 한글로 주석 타입입니다.
요소에서 createComment()메서드를 사용하며 주석내용을 매개변수로 받습니다.
주석은 알고리즘과 별 관계가 없으므로 이렇게 사용하는 경우는 매우 드뭅니다.

Text타입과 같은 원형을 사용하기 때문에 splitText()를 제외한 문자열 메서드를 모두 가집니다.

CDATASection 타입

XML기반 문서 전용입니다.
Text타입과 같은 원형을 사용하기 때문에 splitText()를 제외한 문자열 메서드를 모두 가집니다.

createCDataSection()으로 섹션을 생성합니다.

DcoumentType 타입

<!DOCTYPE>관련 타입이며 자주 쓰이지 않으므로 넘어가겠습니다 :)

DocumentFragment 타입

마크업에 표현되지 않는 유일한 노드 타입입니다.
이 타입은 문서 버퍼 노드를 가지며 경량화된 문서로 정의한다고 합니다.
이해하기 쉽게 설명하자면 매번 Element를 추가하면 렌더링이 계속 발생합니다. 그런이유로 이 타입을 사용하여 버퍼에 한번에 추가할 요소들을 미리 정의해놓고 정의된 요소를 한번에 추가한다면 렌더링을 한번만 발생 시킬 수 있습니다.

var fragment = document.createDocumentFragment();
fragment.appendChild('<div id="div-1"></div>');
fragment.appendChild('<div id="div-2"></div>');
fragment.appendChild('<div id="div-3"></div>');

document.body.appendChild(fragment);

위와같이 div세개를 추가해서 3번 렌더링 될껄 단 한번 렌더링되게 처리가능합니다.

Attr타입

Attribute노드를 직접 참조하는 경우는 드물고 개발자들은 보통 getAttribute(), setAttribute(), rmoveAttribute()를 선호합니다.

그런이유로 위와같이 사용하면 되기때문에 자세한 설명은 넘어가도록 하겠습니다.

DOM다루기

DOM조작은 위에서 본것과 같이 단순합니다.
<script>, <style>, <table> 조작에도 사용할 수 있습니다.
하지만 실제 개발시 스크립트, 스타일, 테이블을 자바스크립트로 조작하는 경우가 많지 않기 때문에 이부분에 대해서는 과감히 생략 하겠습니다.

마치며

DOM시리즈의 1편이 이제 마무리 되었습니다.
DOM확장 및 DOM레벨2와 레벨3에 대한 포스트가 올라올 예정이니 지루하더라도 포기하지말고 끝까지 봐주시면 감사드리겠습니다!
좋은 하루 되세요