2023년 1월 6일

주요 노드 프로퍼티

DOM 노드에 대해 좀 더 알아봅시다.

이번 챕터에선 DOM 노드란 무엇인지, DOM 노드의 주요 프로퍼티는 무엇이 있는지 학습하겠습니다.

DOM 노드 클래스

DOM 노드는 종류에 따라 각각 다른 프로퍼티를 지원합니다. 태그 <a>에 대응하는 요소 노드엔 링크 관련된 프로퍼티를, <input>에 대응하는 요소 노드엔 입력 관련프로퍼티를 제공하죠. 텍스트 노드는 요소 노드와 다른 프로퍼티를 지원하는 것은 말할 필요도 없습니다. 그런데 모든 DOM 노드는 공통 조상으로부터 만들어지기 때문에 노드 종류는 다르지만, 모든 DOM 노드는 공통된 프로퍼티와 메서드를 지원합니다.

DOM 노드는 종류에 따라 대응하는 내장 클래스가 다릅니다.

계층 구조 꼭대기엔 EventTarget이 있는데, Node는 EventTarget을, 다른 DOM 노드들은 Node 클래스를 상속받습니다.

이런 관계를 그림으로 나타내면 다음과 같습니다.

각 클래스는 다음과 같은 특징을 지닙니다.

  • EventTarget – 루트에 있는 ‘추상(abstract)’ 클래스로, 이 클래스에 대응하는 객체는 실제로 만들어지지 않습니다. EventTarget가 모든 DOM 노드의 베이스에 있기때문에 DOM 노드에서 '이벤트’를 사용할 수 있습니다. 자세한 내용은 곧 다룰 예정입니다.
  • Node – 역시 ‘추상’ 클래스로, DOM 노드의 베이스 역할을 합니다. getter 역할을 하는 parentNode, nextSibling, childNodes 등의 주요 트리 탐색 기능을 제공합니다. Node 클래스의 객체는 절대 생성되지 않습니다. 하지만 이 클래스를 상속받는 클래스는 여럿 있습니다. 텍스트 노드를 위한 Text 클래스와 요소 노드를 위한 Element 클래스, 주석 노드를 위한 Comment클래스는 Node클래스를 상속받습니다.
  • Element – DOM 요소를 위한 베이스 클래스입니다. nextElementSibling, children 이나 getElementsByTagName, querySelector 같이 요소 전용 탐색을 도와주는 프로퍼티나 메서드가 이를 기반으로 합니다. 브라우저는 HTML뿐만 아니라 XML, SVG도 지원하는데 Element 클래스는 이와 관련된 SVGElement, XMLElement, HTMLElement 클래스의 베이스 역할을 합니다.
  • HTMLElement – HTML 요소 노드의 베이스 역할을 하는 클래스입니다. 아래 나열한 클래스들은 실제 HTML 요소에 대응하고 HTMLElement를 상속받습니다.
    • HTMLInputElement<input> 요소에 대응하는 클래스
    • HTMLBodyElement<body> 요소에 대응하는 클래스
    • HTMLAnchorElement<a> 요소에 대응하는 클래스
    • 이외에도 다른 클래스가 많은데, 각 태그에 해당하는 클래스는 고유한 프로퍼티와 메서드를 지원합니다.

이렇게 특정 노드에서 사용할 수 있는 프로퍼티와 메서드는 상속을 기반으로 결정됩니다.

<input> 요소에 대응하는 DOM 객체를 예로 들어봅시다. 이 객체는 HTMLInputElement 클래스를 기반으로 만들어집니다.

객체엔 아래에 나열한 클래스에서 상속받은 프로퍼티와 메서드가 있을 겁니다.

  • HTMLInputElement – 입력 관련 프로퍼티를 제공하는 클래스
  • HTMLElement – HTML 요소 메서드와 getter, setter를 제공하는 클래스
  • Element – 요소 노드 메서드를 제공하는 클래스
  • Node – 공통 DOM 노드 프로퍼티를 제공하는 클래스
  • EventTarget – 이벤트 관련 기능을 제공하는 클래스
  • ObjecthasOwnProperty같이 ‘일반 객체’ 메서드를 제공하는 클래스

우리는 앞서 객체는 constructor 프로퍼티를 가진다는 걸 배운 바 있습니다. 이런 특징을 이용하면 DOM 노드 클래스 이름을 확인할 수 있습니다. constructor 프로퍼티는 클래스 생성자를 참조하고 이름은 constructor.name에 저장되어있다는 점을 이용하면 되죠.

alert( document.body.constructor.name ); // HTMLBodyElement

toString을 사용해도 됩니다.

alert( document.body ); // [object HTMLBodyElement]

상속 여부는 instanceof를 사용해 확인할 수 있습니다.

alert( document.body instanceof HTMLBodyElement ); // true
alert( document.body instanceof HTMLElement ); // true
alert( document.body instanceof Element ); // true
alert( document.body instanceof Node ); // true
alert( document.body instanceof EventTarget ); // true

지금까지 살펴본 바와 같이 DOM 노드는 프로토타입을 기반으로 상속 관계를 갖는 일반 자바스크립트 객체입니다.

브라우저 콘솔에 console.dir(elem)를 입력하면 이런 관계를 쉽게 확인할 수 있습니다. HTMLElement.prototype, Element.prototype등이 콘솔에 출력될 겁니다.

console.dir(elem)console.log(elem)의 차이

브라우저 개발자 도구 대부분은 console.logconsole.dir 명령어를 지원합니다. 이 명령어들은 콘솔에 인수를 출력해줍니다. 인수가 자바스크립트 객체라면 두 명령어는 대개 같은 결과를 보여줍니다.

하지만 인수가 DOM 요소일 때는 결과가 다릅니다.

  • console.log(elem)는 요소의 DOM 트리를 출력합니다.
  • console.dir(elem)는 요소를 DOM 객체처럼 취급하여 출력합니다. 따라서 프로퍼티를 확인하기 쉽다는 장점이 있습니다.

document.body를 인수로 넘겨서 그 차이를 직접 확인해보세요.

명세서에서 쓰이는 IDL

명세서에선 DOM 클래스를 자바스크립트를 사용해 설명하지 않습니다. 대신 Interface Description Language(IDL)를 이용해 설명합니다.

IDL에선 모든 프로퍼티 앞에 타입을 붙입니다. DOMStringboolean 같은 타입이 프로퍼티 앞에 붙죠.

명세서 일부에 주석을 달아놓았으니 함께 살펴봅시다.

// HTMLInputElement 정의 시작
// 콜론(:)은 HTMLInputElement가 HTMLElement로 부터 상속되었다는 것을 의미합니다.
interface HTMLInputElement: HTMLElement {
  // <input> 요소와 관련된 프로퍼티와 메서드가 나열되기 시작합니다.

  // 'DOMString'은 프로퍼티 값이 문자열이라는 것을 의미합니다.
  attribute DOMString accept;
  attribute DOMString alt;
  attribute DOMString autocomplete;
  attribute DOMString value;

  // 불린 값(true/false)을 가지는 프로퍼티
  attribute boolean autofocus;
  ...
  // 'void'는 메서드의 리턴값이 없음을 의미합니다.
  void select();
  ...
}

‘nodeType’ 프로퍼티

nodeType 프로퍼티는 DOM 노드의 '타입’을 알아내고자 할 때 쓰이는 구식 프로퍼티입니다.

각 노드 타입은 상숫값을 가집니다.

  • elem.nodeType == 1 – 요소 노드
  • elem.nodeType == 3 – 텍스트 노드
  • elem.nodeType == 9 – 문서 객체
  • 기타 노드 타입에 대한 값은 명세서에서 확인할 수 있습니다.

예시:

<body>
  <script>
  let elem = document.body;

  // 타입을 알아봅시다.
  alert(elem.nodeType); // 1 => 요소 노드

  // 첫 번째 자식 노드
  alert(elem.firstChild.nodeType); // 3 => 텍스트 노드

  // 문서 객체의 타입 확인
  alert( document.nodeType ); // 9 => 문서 객체
  </script>
</body>

모던 자바스크립트에선 노드의 타입을 instanceof나 클래스 기반의 테스트를 이용해 확인하는데, 가끔은 nodeType를 쓰는 게 간단할 때도 있습니다. nodeType은 타입 확인 하는 데만 쓸 수 있고 바꾸지는 못합니다.

nodeName과 tagName으로 태그 이름 확인하기

nodeName이나 tagName 프로퍼티를 사용하면 DOM 노드의 태그 이름을 알아낼 수 있습니다.

예시:

alert( document.body.nodeName ); // BODY
alert( document.body.tagName ); // BODY

그럼 tagNamenodeName의 차이는 없는 걸까요?

물론 있습니다. 미묘하지만 이름에서 그 차이를 유추할 수 있죠.

  • tagName 프로퍼티는 요소 노드에만 존재합니다.
  • nodeName은 모든 Node에 있습니다.
    • 요소 노드를 대상으로 호출하면 tagName과 같은 역할을 합니다.
    • 텍스트 노드, 주석 노드 등에선 노드 타입을 나타내는 문자열을 반환합니다.

nodeName은 모든 노드에서 지원되지만, tagNameElement 클래스로부터 유래되었기 때문에 요소 노드에서만 지원됩니다.

document와 주석 노드를 사용해 tagNamenodeName의 차이점을 확인해 봅시다.

<body><!-- 주석 -->

  <script>
    // 주석 노드를 대상으로 두 프로퍼티 비교
    alert( document.body.firstChild.tagName ); // undefined (요소가 아님)
    alert( document.body.firstChild.nodeName ); // #comment

    // 문서 노드를 대상으로 두 프로퍼티 비교
    alert( document.tagName ); // undefined (요소가 아님)
    alert( document.nodeName ); // #document
  </script>
</body>

요소 노드만 다루고 있다면 tagNamenodeName에는 차이가 없으므로 둘 다 사용할 수 있습니다.

태그 이름은 XML 모드를 제외하고 항상 대문자입니다.

브라우저에서 HTML과 XML을 처리하는 모드는 다릅니다. 웹페이지는 대개 HTML 모드로 처리됩니다. 헤더가 Content-Type: application/xml+xhtml인 XML 문서를 받으면 XML 모드로 문서를 처리합니다.

HTML 모드에선 tagNamenodeName이 모두 대문자로 변경됩니다. <body> 이든 <BoDy>이든 BODY가 되죠.

XML 모드에선 케이스가 ‘그대로’ 유지됩니다. XML 모드는 요즘엔 거의 사용되지 않습니다.

innerHTML로 내용 조작하기

innerHTML 프로퍼티를 사용하면 요소 안의 HTML을 문자열 형태로 받아올 수 있습니다.

요소 안 HTML을 수정하는 것도 가능합니다. innerHTML은 페이지를 수정하는 데 쓰이는 강력한 방법의 하나입니다.

document.body 안의 내용을 출력하고 완전히 바꾸는 예시를 살펴봅시다.

<body>
  <p>p 태그</p>
  <div>div 태그</div>

  <script>
    alert( document.body.innerHTML ); // 내용 읽기
    document.body.innerHTML = '새로운 BODY!'; // 교체
  </script>

</body>

문법이 틀린 HTML을 넣으면 브라우저가 자동으로 고쳐 줍니다.

<body>

  <script>
    document.body.innerHTML = '<b>test'; // 닫는 태그를 잊음
    alert( document.body.innerHTML ); // <b>test</b> (자동으로 수정됨)
  </script>

</body>
스크립트는 실행되지 않습니다.

innerHTML을 사용해 문서에 <script> 태그를 삽입하면 해당 태그는 HTML의 일부가 되긴 하지만 실행은 되지 않습니다.

‘innerHTML+=’ 사용 시 주의점

elem.innerHTML+="추가 html"을 사용하면 요소에 HTML을 추가할 수 있습니다.

아래와 같이 말이죠.

chatDiv.innerHTML += "<div>안녕하세요<img src='smile.gif'/> !</div>";
chatDiv.innerHTML += "잘 지내죠?";

그런데 'innerHTML+='은 추가가 아니라 내용을 덮어쓰기 때문에 주의해서 사용해야 합니다.

기술적으로 아래 두 줄의 코드는 동일한 역할을 합니다.

elem.innerHTML += "...";
// 위 코드는 아래 코드의 축약 버전입니다.
elem.innerHTML = elem.innerHTML + "..."

즉, innerHTML+=는 아래와 같은 일을 합니다.

  1. 기존 내용 삭제
  2. 기존 내용과 새로운 내용을 합친 새로운 내용을 씀

기존 내용이 '완전히 삭제’된 후 밑바닥부터 다시 내용이 쓰여지기 때문에 이미지 등의 리소스 전부가 다시 로딩됩니다.

chatDiv 예시의 chatDiv.innerHTML+="잘 지내죠?" 윗줄의 HTML 내용이 재생성되고 smile.gif 역시 다시 로딩되는 것이죠. 어딘가에 이런 리소스들을 캐싱해 놓았다면 좋았을 거라는 생각이 드는 순간이네요. chatDiv에 텍스트와 이미지가 많이 있었다면 내용을 다시 불러올 때 버벅임이 생기는걸 눈으로 확인하실 수 있을 겁니다.

이 외에도 innerHTML+=은 여러 부작용이 있습니다. 기존에 있던 텍스트를 마우스로 드래그한 상황이라면 내용을 다시 써야 하기 때문에 드래그가 해제될 겁니다. <input> 태그에서 사용자가 입력한 값이 사라지기도 하죠. 부작용 사례는 다양합니다.

innerHTML 말고 HTML을 추가하는 방법을 사용하면 이런 부작용 없이 원하는 HTML을 추가할 수 있는데, 이 방법은 곧 배우도록 하겠습니다.

outerHTML로 요소의 전체 HTML 보기

outerHTML 프로퍼티엔 요소 전체 HTML이 담겨있습니다. outerHTMLinnerHTML에 요소 자체를 더한 것이라고 생각하시면 됩니다.

예시를 살펴봅시다.

<div id="elem">Hello <b>World</b></div>

<script>
  alert(elem.outerHTML); // <div id="elem">Hello <b>World</b></div>
</script>

innerHTML과 달리 outerHTML을 사용해서 HTML을 쓸땐 요소 자체가 바뀌지 않습니다. 대신 outerHTML은 DOM 안의 요소를 교체합니다.

네, 뭔가 이상하게 들리실 겁니다. 실제로도 이상하고요. 그럴 것을 예상하고 구체적인 예시를 준비해 놓았습니다.

코드를 보며 이해해 봅시다.

<div>Hello, world!</div>

<script>
  let div = document.querySelector('div');

  // div.outerHTML를 사용해 <p>...</p>로 교체
  div.outerHTML = '<p>새로운 요소</p>'; // (*)

  // 어! div가 그대로네요!
  alert(div.outerHTML); // <div>Hello, world!</div> (**)
</script>

뭔가 이상합니다.

(*)로 표시한 줄에서 div<p>새로운 요소</p>로 교체했기 때문에 예시를 실행하면 의도한 대로 문서(DOM)에 <div>가 아닌 새로운 내용이 보입니다. 그런데 (**)에서 기존의 div를 출력하네요!

이런 결과가 나타난 이유는 outerHTML에 하는 할당 연산이 DOM 요소(outerHTML 연산의 대상으로, 위 예시에선 변수 div)를 수정하지 않기 때문입니다. 할당 연산은 요소를 DOM에서 제거하고 새로운 HTML 조각을 넣습니다.

즉, div.outerHTML=...는 아래와 같은 일을 합니다.

  • '문서’에서 div를 삭제
  • 새로운 HTML 조각인 <p>A new element</p>을 삭제 후 생긴 공간에 삽입
  • div엔 여전히 기존 값이 저장되어 있고 새로운 HTML 조각은 어디에도 저장되어있지 않음

outerHTML의 이런 동작 방식 때문에 outerHTML을 사용할 땐 실수 할 여지가 많습니다. div.outerHTML을 수정한 후 div에 새로운 내용이 들어갔다고 착각하며 작업하는 경우가 많죠. 정리하자면 이렇습니다. innerHTMLdiv를 수정하지만 outerHTMLdiv를 수정하지 않습니다.

그렇기 때문에 elem.outerHTML에 무언가를 쓸 때는 elem이 수정되지 않는다는 점을 꼭 명심하고 있어야 합니다. 할당받은 HTML은 elem이 있던 공간에 들어갑니다. 새롭게 만들어진 요소를 참조하려면 DOM 쿼리 메서드를 사용합시다.

nodeValue/data로 텍스트 노드 내용 조작하기

innerHTML 프로퍼티는 요소 노드에만 사용할 수 있습니다.

텍스트 노드 같은 다른 타입의 노드에는 innerHTML과 유사한 역할을 해주는 프로퍼티인 nodeValuedata를 사용해야 합니다. 이 두 프로퍼티는 아주 유사하고, 실무에서도 구분 없이 쓰긴 하지만 명세서상에 작은 차이가 있긴 합니다만 data가 좀 더 짧기 때문에 여기선 data를 사용하겠습니다.

텍스트 노드와 주석 노드의 내용을 읽는 예시를 실행해 봅시다.

<body>
  안녕하세요.
  <!-- 주석 -->
  <script>
    let text = document.body.firstChild;
    alert(text.data); // 안녕하세요.

    let comment = text.nextSibling;
    alert(comment.data); // 주석
  </script>
</body>

그런데 이쯤되면 이런 의문이 들 수 있습니다. 텍스트 노드의 내용을 읽거나 수정하는 일은 일어날 법 한데 주석 노드는 왜 이런 기능이 필요한건지 라는 의문이죠.

개발자들은 종종 아래와 같은 방식으로 정보나 지시사항을 HTML에 삽입합니다.

<!-- if isAdmin -->
  <div>관리자로 로그인하였습니다!</div>
<!-- /if -->

이럴 때 data 프로퍼티 기능을 사용해 주석 노드의 내용을 읽고 삽입된 지시사항을 처리하면 유용합니다.

textContent로 순수한 텍스트만

textContent를 사용하면 요소 내의 텍스트에 접근할 수 있습니다. <태그>는 제외하고 오로지 텍스트만 추출할 수 있죠.

예시:

<div id="news">
  <h1>주요 뉴스!</h1>
  <p>화성인이 지구를 침공하였습니다!</p>
</div>

<script>
  // 주요 뉴스! 화성인이 지구를 침공하였습니다!
  alert(news.textContent);
</script>

예시를 실행하면 원래부터 <태그>가 없었던 것처럼 텍스트만 반환되는 것을 확인할 수 있습니다.

그런데 실무에선 텍스트 읽기를 단독으로 쓰는 경우는 흔치 않습니다.

textContent를 사용하면 텍스트를 '안전한 방법’으로 쓸 수 있기 때문에 실무에선 textContent를 쓰기 용으로 유용하게 사용합니다.

사용자가 입력한 임의의 문자열을 다시 출력해주는 경우를 생각해 봅시다.

  • innerHTML을 사용하면 사용자가 입력한 문자열이 ‘HTML 형태로’ 태그와 함께 저장됩니다.
  • textContent를 사용하면 사용자가 입력한 문자열이 ‘순수 텍스트 형태로’ 저장되기 때문에 태그를 구성하는 특수문자들이 문자열로 처리됩니다.

두 프로퍼티를 비교해봅시다.

<div id="elem1"></div>
<div id="elem2"></div>

<script>
  let name = prompt("이름을 알려주세요.", "<b>이보라</b>");

  elem1.innerHTML = name;
  elem2.textContent = name;
</script>
  1. 첫 번째 <div>엔 이름이 'HTML 형태’로 저장됩니다. 입력한 태그는 태그로 해석되어 굵은 글씨가 출력되네요.
  2. 두 번째 <div>엔 이름이 '텍스트 형태’로 저장됩니다. 따라서 입력한 값 그대로 <b>이보라</b>가 출력되는 것을 확인할 수 있습니다.

개발을 하다보면 사용자의 입력값을 받아 처리해야 하는 경우가 많습니다. 이때 사용자가 입력한 값은 텍스트로 처리되어야 합니다. 예상치 못한 HTML이 사이트에 침투하는 것을 막으려면 textContent를 사용합시다.

hidden 프로퍼티

hidden 속성과 hidden 프로퍼티는 요소를 보여줄지 말지 지정할 때 사용할 수 있습니다.

hidden은 HTML 안에서 쓸 수도 있고 자바스크립트에서도 쓸 수 있습니다.

<div>아래 두 div를 숨겨봅시다.</div>

<div hidden>HTML의 hidden 속성 사용하기</div>

<div id="elem">자바스크립트의 hidden 프로퍼티 사용하기</div>

<script>
  elem.hidden = true;
</script>

hidden은 기술적으로 style="display:none"와 동일합니다. 짧다는 점만 다르죠.

hidden을 사용해 요소를 깜빡이게 해봅시다.

<div id="elem">깜빡이는 요소</div>

<script>
  setInterval(() => elem.hidden = !elem.hidden, 1000);
</script>

기타 프로퍼티

지금까지 소개한 프로퍼티 외에도 DOM 요소엔 다양한 프로퍼티가 있는데, 클래스마다 특징적인 프로퍼티 몇 가지를 소개해드리겠습니다.

  • value<input><select>, <textarea>의 값이 저장됩니다. 대응하는 클래스는 HTMLInputElement, HTMLSelectElement 등입니다.
  • href<a href="...">의 href 속성 값이 저장됩니다. 대응하는 클래스는 HTMLAnchorElement입니다.
  • id – id 속성 값이 저장됩니다. 모든 요소 노드에서 사용할 수 있으며, 대응하는 클래스는 HTMLElement입니다.
  • 기타 등등

예시:

<input type="text" id="elem" value="value">

<script>
  alert(elem.type); // "text"
  alert(elem.id); // "elem"
  alert(elem.value); // value
</script>

대부분의 표준 HTML 속성은 그에 대응하는 DOM 프로퍼티를 가지고 있는데, 위 예시와 같은 방식으로 프로퍼티에 접근할 수 있습니다.

특정 클래스에서 지원하는 프로퍼티 전체를 보고 싶다면 명세서를 읽어보면 됩니다. 예를 들어 HTMLInputElement에서 지원하는 프로퍼티 목록은 https://html.spec.whatwg.org/#htmlinputelement에서 찾아볼 수 있습니다.

명세서를 읽지 않고도 개발자 도구의 콘솔 창에 console.dir(elem)를 입력하면 해당 요소에서 지원하는 프로퍼티 목록을 빠르게 확인할 수 있습니다. 개발자 도구의 Elements 패널의 하위 패널 중 'Properties’를 선택해도 동일한 목록을 확인할 수 있습니다.

요약

각 DOM 노드는 고유한 클래스에 속합니다. 클래스들은 계층 구조를 형성합니다. DOM 노드에서 지원하는 프로퍼티와 메서드는 계층 구조에서 어떤 클래스를 상속받느냐에 따라 결정됩니다.

주요 DOM 노드 프로퍼티는 다음과 같습니다.

nodeType
요소 타입을 알고 싶을 때 사용합니다. 요소 노드라면 1을, 텍스트 노드라면 3을 반환합니다. 두 타입 외에도 각 노드 타입엔 대응하는 상숫값이 있습니다. 읽기 전용입니다.
nodeName/tagName
요소 노드의 태그 이름을 알아낼 때 사용합니다. XML 모드일 때를 제외하고 태그 이름은 항상 대문자로 변환됩니다. 요소 노드가 아닌 노드에는 nodeName을 사용하면 됩니다. 읽기 전용입니다.
innerHTML
요소 안의 HTML을 알아낼 수 있습니다. 이 프로퍼티를 사용하면 요소 안의 HTML을 수정할 수도 있습니다.
outerHTML
요소의 전체 HTML을 알아낼 수 있습니다. elem.outerHTML에 무언가를 할당해도 elem 자체는 바뀌지 않습니다. 대신 새로운 HTML이 외부 컨텍스트에서 만들어지고, elem이 삭제된 자리를 채웁니다.
nodeValue/data
요소가 아닌 노드(텍스트, 주석 노드 등)의 내용을 읽을 때 쓰입니다. 두 프로퍼티는 거의 동일하게 동작합니다. 주로 data를 많이 사용하는 편이며 내용을 수정할 때도 이 프로퍼티를 쓸 수 있습니다.
textContent
HTML에서 모든 <태그>를 제외한 텍스트만 읽을 때 사용합니다. 할당 연산을 통해 무언가를 쓸 수도 있는데 이때 태그를 포함한 모든 특수문자는 문자열로 처리됩니다. 사용자가 입력한 문자를 안전한 방법으로 처리하기 때문에 원치 않는 HTML이 사이트에 삽입되는 것을 예방할 수 있습니다.
hidden
true로 설정하면 CSS에서 display:none을 설정한 것과 동일하게 동작합니다.

DOM 노드는 클래스에 따라 이 외에도 다른 프로퍼티를 가집니다. <input> 요소(HTMLInputElement)는 value, type 프로퍼티를, <a> 요소(HTMLAnchorElement)는 href 프로퍼티를 지원하는 것 같이 말이죠. 대부분의 표준 HTML 속성은 대응하는 DOM 프로퍼티를 가집니다.

그런데 HTML 요소와 DOM 프로퍼티가 항상 같은 것은 아닙니다. 관련 내용은 다음 챕터에서 살펴보도록 하겠습니다.

과제

중요도: 5

ulli 노드로 구성된 트리 구조 문서가 있다고 가정해 봅시다.

li 노드 전체를 대상으로 아래와 같은 작업을 하려 합니다. 조건을 만족시킬 수 있는 코드를 작성해 보세요.

  1. li 노드 안에 있는 텍스트를 출력
  2. li 노드 아래에 있는 모든 <li> 태그의 개수를 출력

새 창에서 데모 보기

샌드박스를 열어 정답을 작성해보세요.

<li>를 이용한 반복문을 만들어 봅시다.

for (let li of document.querySelectorAll('li')) {
  ...
}

반복문 안에서 각각의 li 안에 있는 텍스트를 가져와야 합니다.

li의 첫 번째 자식 노드인 텍스트 노드로부터 텍스트를 읽을 수 있습니다.

for (let li of document.querySelectorAll('li')) {
  let title = li.firstChild.data;

  // title은 <li> 안에 있는 다른 노드들보다 앞에 위치한 텍스트입니다.
}

그리고 li.getElementsByTagName('li').length를 이용해 li 노드 아래에 있는 모든 <li> 태그의 개수를 가져올 수 있습니다.

샌드박스를 열어 정답을 확인해보세요.

중요도: 5

스크립트를 실행 결과를 예측해보세요.

<html>

<body>
  <script>
    alert(document.body.lastChild.nodeType);
  </script>
</body>

</html>

함정이 있는 문제였습니다.

<script>가 실행되는 시점엔 브라우저가 <script> 아래에 있는 문서를 처리하지 못했기 때문에 가장 마지막 DOM 노드는 <script> 자기 자신입니다.

따라서 얼럿창엔 1(요소 노드)이 출력됩니다.

<html>

<body>
  <script>
    alert(document.body.lastChild.nodeType);
  </script>
</body>

</html>
중요도: 3

스크립트를 실행 결과를 예측해보세요.

<script>
  let body = document.body;

  body.innerHTML = "<!--" + body.tagName + "-->";

  alert( body.firstChild.data ); // 얼럿 창엔 어떤 내용이 출력될까요?
</script>

**BODY**가 출력됩니다.

<script>
  let body = document.body;

  body.innerHTML = "<!--" + body.tagName + "-->";

  alert( body.firstChild.data ); // BODY
</script>

차근차근 설명해보겠습니다.

  1. <body>의 콘텐츠가 <!--BODY-->로 대체됩니다. body.tagName"BODY"이기 때문입니다. tagName은 항상 대문자라는 점을 잊지 마세요.
  2. <body>의 콘텐츠가 교체되면서 주석이 유일한 자식 노드가 됩니다. 따라서 body.firstChild을 사용해 주석을 얻을 수 있게 됩니다.
  3. 주석 노드의 data 프로퍼티엔 주석 내용(<!--...--> 안쪽의 내용)이 저장됩니다. 따라서 data 프로퍼티의 값은 "BODY"입니다.
중요도: 4

document는 어떤 클래스에 속할까요?

DOM 계층 구조에서 document이 속한 클래스는 어디에 위치해 있을까요?

이 클래스는 Node, Element, HTMLElement 중 어떤 클래스를 상속받을까요?

document 노드가 어떤 클래스에 속해있는지는 아래와 같은 스크립트를 통해 확인할 수 있습니다.

alert(document); // [object HTMLDocument]

아래 스크립트로도 확인할 수 있습니다.

alert(document.constructor.name); // HTMLDocument

살펴본 바와 같이 documentHTMLDocument 클래스의 인스턴스입니다.

그렇다면 HTMLDocument 클래스는 DOM 계층 구조에서 어디에 있을까요?

명세서를 확인하면 알 수 있긴 하지만, 직접 만든 코드를 사용하면 위치를 더 빨리 알아낼 수 있습니다.

__proto__를 사용해 프로토타입 체인을 거슬러 올라가 봅시다.

아시다시피 클래스의 메서드는 생성자의 prototype에 구현되어있습니다. document의 메서드는 HTMLDocument.prototype에 구현되어 있죠.

여기에 더하여 prototype 안에는 생성자 함수의 참조 역시 저장되어 있습니다. 이를 확인해 봅시다.

alert(HTMLDocument.prototype.constructor === HTMLDocument); // true

클래스 이름을 얻으려면 constructor.name를 사용하면 됩니다. Node 클래스를 만날 때까지 document의 프로토타입 체인 전체를 거슬러 올라가 보겠습니다.

alert(HTMLDocument.prototype.constructor.name); // HTMLDocument
alert(HTMLDocument.prototype.__proto__.constructor.name); // Document
alert(HTMLDocument.prototype.__proto__.__proto__.constructor.name); // Node

위와 같은 코드를 사용해 계층 구조를 파악할 수 있습니다.

이 외에도 개발자 도구에서 console.dir(document)를 사용해 객체를 검사하고, __proto__에 저장된 정보를 통해 이름을 알아낼 수도 있습니다. 브라우저 콘솔 내부에서 자동으로 constructor의 이름을 추출하기 때문입니다.

튜토리얼 지도

댓글

댓글을 달기 전에 마우스를 올렸을 때 나타나는 글을 먼저 읽어주세요.
  • 추가 코멘트, 질문 및 답변을 자유롭게 남겨주세요. 개선해야 할 것이 있다면 댓글 대신 이슈를 만들어주세요.
  • 잘 이해되지 않는 부분은 구체적으로 언급해주세요.
  • 댓글에 한 줄짜리 코드를 삽입하고 싶다면 <code> 태그를, 여러 줄로 구성된 코드를 삽입하고 싶다면 <pre> 태그를 이용하세요. 10줄 이상의 코드는 plnkr, JSBin, codepen 등의 샌드박스를 사용하세요.