2015년 10월 6일 화요일

[JavaScript] 문서 객체 모델(DOM, Document Object Model)

문서 객체 모델(DOM, Document Object Model)은 HTML과 XML 문서에 사용되는 API(Application Programming Interface)이다. DOM은 주어진 문서를 노드(node)를로 이루어진 계층구조의 트리(hierarchical tree)로 나타내며, 원하는 노드를 추가, 제거, 그리고 수정할 수 있게 한다. 이런 DOM의 가장 초기 버젼인 DOM Level 1은 1998년 W3C에 처음 표준으로 도입되었으며 현재 거의 모든 웹브라우져들, Chrome, Firefox, Safari, Internet Explorer, Microsoft Edge, Opera 등에 사용된다.

DOM Tree의 Node

각각의 태그들로 구성된 HTML 문서와 XML 문서를 Tree 형태로 표현하기 위해서 Node 들은 각각의 태그들을 구성하게 된다. 이 때문에 각각의 태그들은 그에 알맞는 Type을 가진 Node 행태로 저장되며, 서로 다른 Type의 Node 들은 그만의 특징, 데이터, 메소드, 다른 노드들과의 관계를 가지게 된다.

Document Node 와 Node Type

Document Node는 모든 Node들의 최상위에 위치하는 Root 노드이다.


<!DOCTYPE html>
<html>
 <head>
  <title>DOM Example</title>
 </head>
<body>
 <p>This is HTML Example</p>
</body>
</html>


예를들어 위의 html 코드를 DOM Tree 형식으로 표현한다면 아래와 같은 형태를 띄게된다.


Document Node 가 최상위에 위치하며 그 바로 아래 HTML Element Node 가 위치한다. Document Node는 최상위 Node로 문서내 모든 Node들의 ownerDocument 프로퍼티가 가리키는 node이다.
HTML Element Node는 Document Element 라 불리기도 하며, 다른 Element Node들의 최 상위에 위치하는 Element Node이다. 그렇기 때문에 Document Node는 단 1개의 Document Element를 자기 child 로 가지며, Document Element는 다른 element 들을 자기 children으로 가지게 된다.

HTML문서의 경우에는 html 태그가 Document Element이며, 이렇게, 모든 markup들은 Node로 Tree에 저장된다. 대부분의 markup 들은 Element Node 가 되며, attribute 들은 attribute node, markup에 감싸진 텍스트 들은 text node, 주석들은 comment node가 된다. 이렇게 총 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
각각의 타입은 숫자로 표현될 수 있다. 모든 Node 들은 nodeType 프로퍼티를 가지므로 위의 numeric constant로 비교가 가능하다.


if(node.nodeType == Node.ELEMENT_NODE){
 console.log("This is Element Node");
}


nodeType 프로퍼티가 node의 타입을 가르쳐 준다면, nodeName 프로퍼티는 해당 node의 태그이름을 저장하는 값이다.
nodeName 값과 nodeType 값은 node의 타입에 관계없이 모든 node가 가지고 있는 프로퍼티이다.


document.images[0].nodeName
//"IMG"
document.images[0].nodeType
//1


DOM(Document Object Model) 수정하기

모든 tree 구조가 그렇듯, 각각의 node 들은 다른 node 들과 특정 관계(Parent-Child, Siblings, First Child, Last Child)에 있다. 이러한 관계를 통해 개발자는 문서 객체 모델을 조금 더 쉽게 수정 가능하다.
우선 모든 Node 들은 자신의 프로퍼티로 childNodes라는 프로퍼티를 가지는데 이는 자신의 자식 node들의 리스트를 저장하는 프로퍼티 이다.childNodes 프로퍼티는 자식 노드들을 NodeList 타입의 자료구조로 저장한다. NodeList는 Array와 매우 비슷한 자료 구조로, Array와 마찬가지로 괄호([])를 이용해 내부 데이터에 접근이 가능한 구조이다. 하지만 Array Constructor를 통해 생성된 인스턴스는 아님을 염두에 두자. NodeList 객체는 실시간으로 DOM 스트럭쳐와 반응하며, 이때문에 NodeList에 생기는 변화는 곧바로 DOM 구조, 웹문서와 직결된다. NodeList 타입은 Array와 마찬가지로 []를 사용할 수도 있고, item 메소드를 이용해 접근할수도 있다.


node.childNodes[0];
node.childNodes.item(1);
node.childNodes.length;
node.firstChild == node.childNodes[0];//true
node.lastChild == node.childNodes[node.childNodes.length];//true


firstChild, lastChild 프로퍼티를 이용해 NodeList의 처음과 마지막 node에 접근이 가능하다.
또한 childNodes 프로퍼티와 마찬가지로 parentNode는 해당 Node의 상위 Node를 가르키는 포인터 역할을 한다. childNodes 리스트에 속한 Node들은 서로와 sibling 관계에 있으며 nextSibling, previousSibling 프로퍼티를 이용해 전 node, 다음 node로 접근이 가능하다.


node.childNodes[0].previousSibling;//null
node.childNodes[0].nextSibling;// == node.childNodes[1]
node.childNodes[node.childNodes.length-1].nextSibling;//null


리스트의 처음과 끝에 있는 node들은 previousSibling, nextSibling 프로퍼티로 null 값을 가지게 된다. 이런 포인터들은 모두 read-only 값을 가진 프로퍼티이기 때문에 위 프로퍼티들을 통해 직접적은 node 수정은 불가능하다.
그 때문에 수정시에는 DOM 수정을 위핸 메소드들을 이용해야한다.

Node 추가, 수정, 제거하기

appendChild() 메소드는 해당 노드의 childNodes 리스트의 마지막에 새로운 Node를 추가하고 추가된 node를 반환하는 메소드이다.


var newlyAddedNode = node.appendChild(newNode);
newlyAddedNode == newNode//true
node.lastChild == newNode//true


insertBefore() 메소드는 원하는 노드의 앞에 새로운 노드르 삽입하는 메소드이다. 새로 추가하는 노드, 삽입 위치의 뒤에있는 노드, 이렇게 2개의 argument를 가지며 새로 삽입된 노드를 반환한다.


var newlyAddedNode = node.insertBefore(newNode,node.childNodes[0]);
newlyAddedNode == newNode//true
node.firstChild == newNode//true
//만약 두번째 argument가 주어지지 않거나 null이 되면 nodeList의 마지막에 삽입된다.


replaceNode() 메소드는 원하는 노드를 새로운 노드로 교체하는 메소드 이다. 새로 교체할 노드, 교체될 노드, 이렇게 2개의 argument를 받으며 교체된 노드가 반환된다.


var oldNode = node.replaceNode(newNode,node.childNodes[0]);


removeNode() 메소드는 원하는 노드를 제거하는 노드이다. argument로는 제거할 노드를 가리키는 포인터를 받으며, 제거된 노드가 반환된다. 이렇게 제거된 node들은 제거된 이후에도 document에 의해 소유된다. 하지만 HTML Element 하위 트리에 속해있지 않기 때문에 사용자에게 보여지지 않는다.


var removed = node.removeNode(node.childNodes[0]);


Node 복사하기

문서 객체 모델 구조의 특성상 1개의 노드는 2개의 위치에 존재할 수 없다. 만약 그렇다면 이는 해당 노드의 포인터 프로퍼티가 2개의 값을 가지고 있음을 뜻하며 현실적으로 불가능한 일이다. 그렇기 때문에 이미 문서에 추가된 노드를 다른 위치에 삽입하려 시도하게 되면 원래 추가되었던 노드가 제거되는 일이 발생한다.


var oldNode = node.childNodes[0];
node.appendChild(oldNode);
node.childNodes[0] == oldNode;// false
node.lastChild == oldNode;// true


이러한 이유 때문에 이미 추가된 node를 문서의 다른 위치에 추가하기 위해서는 같은 프로퍼티를 가진 새로운 node 인스턴스를 생성해야할 필요가 있다. cloneNode() 메소드는 이러한 작업을 충실하게 수행하는 메소드이다. 이 메소드는 boolean 값 1개만을 argument로 받으며 이 boolean 값은 node의 하위 sub-tree까지 모두 포함하여 복사할지(true), 아니면 해당 node만 복사할지(false)를 정하는 argument 이다.


var clone1 = node.cloneNode(true);
var clone2 = node.cloneNode(false);


normalize() 메소드

normalize() 메소드는 메소드가 실행된 노드의 sub-tree에서 empty text node를 제거하는 메소드이다.normalize 메소드는 이런 empty text node를 제거하고, 여러개의 text node가 sibling 관계로 존재할시 해당 node들을 병합(merge)하는 메소드 이다.


<!DOCTYPE html>
<html>
 <head>
  <title>DOM Example</title>
 </head>
<body>
 <p id = "paragraph">
  This is HTML Example.
 </p>
</body>
</html>


위의 html 코드에서 p태그에 새로운 text를 추가한후에 childNodes 리스트를 보게되면 2개의 text node가 sibling 관계로 존재하는걸 알 수 있다.이를 normalize 메소드를 이용해 합쳐주자


document.getElementById("paragraph").appendChild(document.createTextNode("NewLine"));
document.getElementById("paragraph").childNodes;//["This is HTML Example.", "NewLine"]
//이를 normalize 메소드를 이용해 합쳐주자
document.getElementById("paragraph").normalize();
document.getElementById("paragraph").childNodes;//["This is HTML Example.NewLine"]

댓글 없음:

댓글 쓰기