문서 객체(Document Object Model)

재이·2022년 2월 8일
0

Javascript

목록 보기
7/7

※ 본 포스팅은 책 『실전 프로젝트 반응형 웹 퍼블리싱』의 내용을 바탕으로 작성되었음

문서 객체(Document Object Model)

DOM(Document Object Model)이란 '문서 객체'들을 일컫는다. 웹페이지의 구조는 <html>이 모든 태그들을 감싸고 있으며, 그 내부는 다시 각종 설정과 선언으로 이워진 <head>요소와 본문 내용이 모두 담긴 <body>요소로 나누어진다. 그리고 <body>요소로 나누어진다. 그리고 <body>요소 안에는 <div>,<p>,<ul>,<form>,<table> 등의 요소들이 배치된다.

선택자

특정 문서 요소(객체)에 스타일을 적용하거나 삭제하려면 그 문서 요소에 접근하는 방법을 알아야 한다. 요소 선택자에는 '원거리 선택자''근거리 선택자'가 있다.

요소에 포함되는 id 속성 또는 요소(태그)명 등을 이용하여 직접 접근하는 방식을 원거리 선택자라고 하며, 선택된 요소를 기준으로 그 가까운 요소를 선택하고 싶을 때에는 근거리 선택자를 이용한다.

일반적으로, 요소에 접근할 때에는 우선 원거리 선택자를 이용하여 접근한 후 근거리 선택자를 이용하여 그 관련 요소들을 선택한다.

다음 실습예제를 통하여 원거리 선택자와 근거리 선택자에 대해서 상세히 알아보자.

예제1

<body>
  <div>
	<h1>선택자</h1>
	<h2 id="title">원거리 선택자</h2>
	<ul>
	  <li>getElementById</li>
	  <li>getElementsByTagName</li>
	</ul>
	<h2 id="title2">근거리 선택자</h2>
	<ul id="list">
	  <li>parentNode</li>
	  <li>childNodes</li>
	  <li>children</li>
	  <li>firstChild</li>
	  <li>previousSibling</li>
	  <li>nextSibling</li>
	</ul>
  </div>
 </body>
</html>

원거리 선택자

크게 'id 선택자(getElementById)''요소명 선택자(getElementsByTagName)'가 있다.

id 선택

태그에 지정한 id 속성값을 이용하여 특정 요소에 접근할 수 있는 선택자이다.

**선택대상.getElementById("아이디명");

그럼, 예제1에서 연습한 HTML 중 'title'이라는 id의 <h2>요소에 접근한 후 다음과 같이 스타일을 적용하여 그 내부 텍스트의 폰트 색상을 바꿔 보도록 하자.

var myObj = **document.getElementById("title");		//아이디를 이용하여 요소 선택
myObj.style.color = "#f00";							//폰트 색상 스타일 적용

단, 7과 '이벤트'에서 언급했듯 자바스크립트는 늘 <body>보다 앞서는 <head> 영역에 정의되어 문서 요소들을 불러오기 전에 실행되므로, <body> 내 요소들에 대한 선택자를 사용할 때에는 반드시 '전체 문서를 로딩 후 실행'하라는 이벤트 조건을 등록해야 한다.

function loadFnc() {
  var myObj = **document.getElementById("title");		//id를 이용하여 요소 선택
  myObj.style.color = "#f00";
}
if(window.addEventListener){
  window.addEventListener("load", loadFnc, false);		//표준 방식 브라우저
}else id(window.attachEvent){
  window.addEventListener("onload",loadFnc);			//IE8 이하 버전
}

요소(태그)명 선택자

요소(태그)명을 이용해서 접근할 수 있는 선택자인데, 이때 같은 이름의 태그들이 여럿 존재하면 이들이 모두 '배열'로 저장된다. 따라서 getElements와 같이 's'가 붙는다.

선택대상.getElementsByTagName("태그명");

우선 예제1의 HTML 중 첫 번째 <ul>요소에 접근한 후, 그 하위 요소 중 두 번째 <li> 요소에 border(경계선)를 생성해 보자.

var myList = **document.getElementsByTagName("ul");** --- 1. ul 요소들 선택
var myObj = **myLest[0].getElementsByTagName("li")[1];** --- 2. ul > li 요소들 선택

myObj.style.border = "solid 1px #f00"; --- 3. 선 스타일 적용

  • myList = document.getElementsByTagName("ul");
    예제1의 HTML을 보면 <ul>요소가 모두 2개가 존재한다. 따라서 myList라는 변수는 자동으로 배열 객체가 되며 다음과 같이 <ul>요소 2개가 차례로 저장된다.

    myList = 0 : <ul> / 1 : <ul>

  • var myObj = myList[0].getElementsByTagName("li")[1];
    MyList에는 <ul>요소 객체 2개가 저장되어 있다. 여기서 0번 인덱스(첫번째)의 <ul>요소를 '선택대상'으로 삼은 후, 그 하위에 있는 <li>요소들 중 1번 인덱스(두번째)를 선택했다. 그렇게 선택된 <li>요소를 myObj라는 이름으로 저장한 것이다.

  • myObj.style.border = "solid 1px #f00";
    지정된 <li>요소에 1px 두께와 빨간색(#f00) 선(border)을 실선(solid)으로 생성한다.

그럼, 한 번만 더 해보자

이번에는 두 번째 <ul>요소의 하위 요소 중 네 번째 <li>요소 안에 포함된 'firstChild'라는 텍스트의 색을 파란색으로 바꿔보자.

바로 앞에서와 같이 '요소명 선택자'를 활용해 배열로 저장하고 인덱스 번호를 통해 다시 한 번 골라내는 식으로도 가능하지만, 잘 살펴보면 우리가 접근해야 할 두 번째 <ul>요소에는 미리 'list'라는 id를 부여해뒀다.

즉, 먼저 'id명 선택자(getElementById)'로 'list'라는 id의 <ul>요소를 변수로 저장(예:listZone)한 후, 비로소 그 아래의 <li>요소들을 '요소명 선택자(getElementsByTagName)'를 활용하여 배역 객체(예:listObj)로 저장한다.

그럼 다음과 같은 배열 객체가 되는데, 이 중 'firstChild'라는 텍스트를 지닌 <li> 요소는 네 번째 <li>이므로 아래에서와 같이 인덱스 3번([3])을 선택하면 되겠다.

listObj = 0 : <li> / 1 : <li> / 2 : <li> / 3 : <li> / 4 : <li> / 5 : <li>

그리고 마지막으로 스타일을 적용하면 된다. 이 모든 과정을 적용한 스크립트는 다음과 같이 작성하면 되겠다.

var listZone = **document.getElementById("list");** --- 1. id가 'list'인 요소 선택
var listObj = **listZone.getElementsByTagName("li")[3];** --- 2. `<li>`중 인덱스 3번 선택
listObj.style.color = "blue"; --- 3. 선택 요소에 글자색 적용

근거리 선택자

특정 요소를 기준으로 상대적인 다른 요소를 선택할 때 사용하며, 다음과 같다.

parentNode --- 선택된 요소 기준으로 부모 요소를 선택할 때 사용
childNodes --- 선택된 요소 기준으로 자식 요소를 선택할 때 사용
children --- 선택된 요소 기준으로 자식 요소를 선택할 때 사용
firstChild --- 선택된 요소 기준으로 첫 번째 자식 요소를 선택할 때 사용
lastChild --- 선택된 요소 기준으로 마지막 자식 요소를 선택할 때 사용
previousSibling --- 선택된 요소 기준으로 형제 요소 중 이전 요소를 선택할 때 사용
nextSibling --- 선택된 요소 기준으로 형제 요소 중 ㄷ다음 요소를 선택할 때 사용
tagName (속성) --- 선택된 요소의 태그명을 반환(요소 노드의 속성)
nodeValue (속성) --- 선택된 요소의 텍스트를 반환(텍스트 노드의 속성)

parentNode(부모 요소 선택자)

id가 'list'인 <ul>요소를 시준으로 그 상위의 <div> 요소에 '근거리 선택자'를 통해 접근하려면 parentNode(부모 요소)를 사용할 수 있다.

document.getElementById("list").parentNode;`<div>`요소

그럼, <ul>의 입장에서는 전체를 감싸는 <body>도 부모가 아닐까?
그렇지 않다. 부모 요소는 오로지 '바로 상위'의 한 개만 존재한다. 따라서 <ul>에게는 오로지 <div>만 부모가 될 수 있다.

childNodes(자식 요소 선택자)

역시 id가 'list'인 <ul>요소를 기준으로 그 하위의 <li>들을 '근거리 선택자'로 접근하려면 'childNodes 또는 children'을 사용한다. 먼저 childNodes의 사용법은 다음과 같다.

childObj = document.getElementById("list").chiildNodes;`<li>`요소 6(배열)

이때, 표준 방식 브라우저에서는 인덱스 0,2,4,6,8,10,12번 저장소에 공백 문자가 저장되지만,

childObj = 0:" " / 1:<li> / 2:" " / 3:<li> / 4:" " / 5:<li> / 6:" " / 7:<li> / 8:" " / 9:<li> / 10:" " / 11:<li> / 12: " "

비표준 방식 브라우저(IE8 이하 버전)에서는 다음과 같이 공백을 무시한다.

childObj = 0:<li> / 1:<li> / 2:<li> / 3:<li> / 4:<li> / 5:<li>

위와 같이 서로 다르게 저장되는 이유는 무엇일까?
선택자가 호환되지 않아서인데, 먼저 다음의 HTML 코드를 보도록 하자.

<ul id="list"> (Enter)
  <li>parentNode</li> (Enter)
  <li>childNodes</li> (Enter)
  <li>children</li> (Enter)
  <li>firstChild</li> (Enter)
  <li>previousSibling</li> (Enter)
  <li>nextSibling</li> (Enter)
</ul>

태그를 작성할 때에는 기본적으로 줄바꿈을 하게 되는데, 이런 상태에서 childNodes를 사용하면 표준 방식의 브라우저는 저 줄바꿈 하나를 모두 '공백 문자'로 인식하여 자식 요소로 가져오는데, IE8 이하 버전에서는 줄바꿈을 무시하게 되어 있는 것이다.

그럼 표준 브라우저에서의 호환을 위해 태그를 모두 붙여서 써야 할까? 그럼 코드의 가독성이 떨어질 것이다. 이런 문제로 childNodes 대신 children이 존재하는 것이다.

children(자식 요소 선택자)

그럼 이번에는 childNodes와 같은 자식 요소 선택자, children의 사용법을 보도록 하자.

childObj = document.getElementById("list").children;

children은 childNodes와 달리 줄바꿈을 무시하고 다음과 같이 요소 노드만 선택한다.

childObj = 0:<li> / 1:<li> / 2:<li> / 3:<li> / 4:<li> / 5:<li>

이때 childObj 배열 변수에 저장된 요소명을 출력해보자

for(var i = 0; i < childObj.length; i++){
  document.write(childObj[i].tagName + "<br />");
}

출력값
Li
Li
Li
Li
Li

이렇게, 호환성을 위해 자식 요소들을 선택해야 할 때에는 chidren을 권장한다.

firstChild(첫 번째 자식 요소 선택자)

childNodes나 children과 같이 자식 요소 모두를 배열로 선택하는 것이 아니라, 여러 자식 요소 중 첫 번째만 '근거리 선택자'로 접근할 때에는 firstChild 선택자를 이용한다.

document.getElementById("list").firstChild;

이때에도 역시 표준 방식 브라우저에서는 HTML 태그 내의 줄바꿈을 공백 문자(텍스트)로 인식하여 첫 번째 자식 요소로서 가져온다. 그러나 IE8 이하 버전에서는 줄바꿈을 무시한 채 정상적인 첫 번째 <li> 요소를 가져오게 된다.

그럼 호환이 되지 않는 firstChild에 대한 해결 방법은 없을까?
결국, children을 이용해 0번 인덱스 요소를 가져와야 한다.

document.getElementById("list").children[0];

lastChild(마지막 자식 요소 선택자)

앞서 firstChild와 반대로 마지막 자식 요소만 선택할 때에는 lastChild 선택자를 사용한다.

document.getElementById("list").lastChild;

하지만, lastChild 역시 표준 방식 브라우저에서의 호환성 문제가 생긴다. 따라서 이때에도 역시 children을 사용해 자식 요소들을 배열로 가져온 후, 그 마지막 인덱스 번호를 통해 마지막 자식 요소를 가져와야 하겠다. 자식 요소가 모두 몇개인지 잘 모를 경우, 먼저 해당 배열 변수의 마지막 인덱스 번호를 구한(length-1 메서드) 후 활용하면 된다.

var lastNum = document.getElementById("list").children.length-1; // 요소갯수-1
document.getElementById("list").children[lastNum];

prevSibling(이전 형제 요소 선택자)

대상 요소와 같은 등금에 해당하는 요소들(형제 요소들) 중, 대상 요소의 바로 앞 요소를 선택할 때 prevSibling 선택자를 사용한다. 여기서는 예제1의 HTML 요소 중 id값이 'title'인 <h2>요소를 기준으로, 바로 앞의 형제 요소를 선택해 보겠다.

var prev = document.getElementById("title").previousSibling;

그런데 이 prevSibling 선택자 역시 브라우저의 종류에 따라 결과가 다르다. 이유는 앞에서와 마찬가지로, 태그 사용 시의 줄바꿈을 '공백'이라는 하나의 '문자(텍스트)'로서 인식하는 표준 방식 브라우저들의 처리 방식 때문이다.

이런 이유로 자식 요소를 구해오는 childNodes를 비롯해 firstChild와 lastChild까지, 모두 호환성 문제의 해결을 위해 children을 활용하여 문제를 해결해봤지만, '형제' 요소에 대해서는 children 조차 해답이 될 수 없다.

따라서, 이제는 보다 원론적인 해결책'노드 타입'의 활용법에 대해 알아보자.

노드 타입(node Type)

어떤 노드가 '요소(객체)'와 '속성', 혹은 '텍스트' 중 어떤 종류인지 나누는 기준을 '노드 타입'ㅇ라고 한다. 각 노드 타입은 그 종류에 따라 다음과 같이 다른 값을 지닌다.

요소 노드 --- 1
속성 노드 --- 2
텍스트 노드 --- 3

바로 앞 'prevSibling' 선택자에서 예로 든 변수 'prev'를 다음과 같이 출력해 보면, 표준 브라우저에서는 '공백 문자'로 저장되어 노드 타입 값이 '3'으로 출력된다. 반면, 비표준브라우저(IE)에서는 <h1>요소(객체)가 저장되어 그에 맞게 '1'이 출력되겠다.

var prev = document.getElementById("title").previousSibling;
document.write(prev.nodeType);				// 출력값 : 표준 = 3, 비표준 = 1

이 '노드 타입'을 활용하여, 그 값이 '요소(객체)'에 해당하는 '1'이 될 때까지 prevSibling(이전 형제 요소 선택자)이나 다음에 학습할 nextSibling(다음 형제 요소 선택자)을 반복 실행하도록 하면 원하는 요소를 선택할 수 있을 것이다.

var prev = document.getElementById("titlt").prevSibling;		// 1. prevSibling 선택자
while(prev.nodeType != 1){										// 2. '요소'가 아니면 반복
  prev = prev.previousSibling;

표준 브라우저에서는 prev 변수에 공백 문자가 저장될 것이므로(노트 타입 3) while문 조건을 만족하여 반복문을 실행하게 된다. 이때 실행문은 다시 prevSibling 선택자를 적용시키고, 그 결과 <h1>요소 노드가 선택되어 prev에 저장된다. 그 후에 다시 while 문으로 조건을 검사하면 노드 타입이 '1'로 인식되어 반복을 마치게 되는 것이다.

nextSiblibng(다음 형제 요소 선택자)

특정 요소를 기준으로 그 형제 요소들 중 바로 다음 요소를 선택하려면 nextSibling 선택자를 사용한다. 기본적인 사용법은 앞에서 학습한 'prevSibling'의 경우와 같다.

var next = document.getElementById("title").nextSibling;

이 또한 호환성 문제를 지니므로, 역시 '노드 타입'을 활용한 반복문으로 해결한다.

var next = document.getElementById("title").nextSiblibng;		// 1. nextSibling 선택자
while(next.nodeType != 1){
  prev = prev.nexSibling;
}

문서 객체 조작

지금까지 문서 내의 요소를 선택하는 방법을 연습했다면, 이제는 문서에 새로운 요소를 직접 생성하여 추가, 삭제, 복제하는 방법에 대해 알아보자.

문서 객체 조작에 관련된 메서드

생성

  • creatElement("요소명") - 새 요소의 생성
  • appendTextNode / createTextNode("텍스트") - 새 텍스트의 생성

추가

  • 선택 요소1.appendChild(새 요소, 선택 요소2) - '선택 요소1'에 새로운 자식 요소를 추가
  • 선택 요소1.insertBefore(새 요소, 선택 요소2) - '선택 요소1'의 자식인 '선택 요소2' 앞에 새로운 자식 요소를 추가

교체

  • 선택 요소1.replaceChild(새 요소, 선택 요소2) - '선택 요소1'의 자식인 '선택 요소2'를 새 요소로 덮어씌움

삭제

  • 선택 요소1.removeChild(선택 요소2) - '선택 요소1'의 자식인 '선택 요소2'를 삭제

복제

  • 선택 요소.cloneNode(true or false) - '선택 요소'를 복제하여 true인 경우에는 하위 요소까지 모두 복제

속성부여

  • 선택 요소.setAttribute("속성", "값") - '선택 요소'에 해당 속성값 부여

문서 요소의 생성 순서

  • 문서에 필요한 요소들에 대해 알아본다.
    <ul>요소 1개와 그 하위의 <li>요소 2개, 텍스트 2개가 필요하다고 가정하자.
<ul>
  <li>리스트1</li>
  <li>리스트2</li>
</ul>
  • 필요한 객체들을 생성한다.
    creatElement 메서드를 사용하여 새 요소를 생성한다. 그리고 creatTextNode 혹은 appendTextNode 메서드를 사용하여 텍스트 객체를 생성한다.
  • 생성된 객체를 구조화한다.
    <li>요소에 텍스트를 자식 요소로 추가하고, <li>요소들을 <ul>요소에 자식 요소로 편입시킨다.
  • 문서의 <body>요소 내에 완성된 <ul>요소를 추가한다.

그럼, 다음의 두 코드를 보며 생략되었던 버튼을 생성해보자.

<body>
  <div id="theBox">
    <h1>요소 생성 연습</h1>.
  </div>
</body>
<p>
  <button id="btn1">버튼1</button>
  <button id="btn2">버튼2</button>
</p>

다음 실습을 통해 문서 객체를 생성하고 구조화하여 HTML에 추가하여 보겠다.

<script type="text/javascript">
  //<![CDATA[
    window.onload = function() {
  
  	  // 요소 객체 생성
      var newPtag = document.createElement("p");
      var newButton1 = document.createElement("button");
      var newButton2= document.createElement("button");
      var text1 = document.createTextNode("버튼1");
      var text2 = document.createTextNode("버튼2");
  
      // 속성 생성
      newButton1.setAttribute("id", "btn1");
      newButton2.setAttribute("id", "btn2");
  
      // 문서 객체 구조화
      newButton1.appendChild(text1);
      newButton2.appendChild(text2);
      newPtag.appendChild(newButton1);
      newPtag.appendChild(newButton2);
  
      // 선택 요소에 새 요소 추가
      var theObj = document.getElementById("theBox");
      theObj.appendChild(newPtag);
	}
  //]]>
</script>

그럼 이번에는, 실습을 통해 insertBefore, removeChild, cloneNode에 대해 알아보자.

<body>
  <h1>문서 객체 조작</h1>
  <ul id="thelist">
    <li>리스트2</li>
    <li>리스트3</li>
    <li>리스트4</li>
    <li>리스트1</li>
  </ul>
</body>

우선 위 HTML 중 <li>리스트</li>을 복제 후 삭제한다. 그리고 복제한 요소를 <li>리스트2</li> 이전으로 추가해야 보기 좋겠다.

<script type="text/javascript">
  //<![CDATA[
    window.onload = function(){
  	  var theList = document.getElementById("theList");
  	  var listAll = theList.getElementByTagName("li");
  	  
  	  // 1. <li>리스트1</li>을 복제한다.
  	  var copyList = listAll[3].cloneNode(true);
  	
  	  // 3. 복제된 <li>리스트1</li>를 '리스트2' 앞으로 삽입한다.
  	  theList.insertBefore(copyList, listAll[0]);
	}
  //]]>
</script>
	// 2. 기존의 <li>리스트1</li>을 삭제한다.
	theList.removeChild(listAll[3]);
profile
그림쟁이 개발자

0개의 댓글