📙 (JAVASCRIPT)

자바스크립트) DOM

놀러와요 버그의 숲 2022. 1. 3. 05:40
728x90
반응형

본글은 자바스크립트 딥다이브 39장 DOM 파트를 요약한 내용입니다 (p.677~ p.709)

 

 

브라우저의 렌더링 엔진은 HTML 문서를 파싱하여 브라우저가 이해할 수 있는 자료구조인 DOM을 생성한다.

 

 

DOM (Document Object Model)

: HTML 문서의 계층적 구조와 정보를 표현하여 이를 제어할 수 있는 API, 즉 프로퍼티와 메서드를 제공하는 트리 자료 구조다.

 

 

39.1  노드 

 

HTML 요소는 HTML 문서를 구성하는 개별적인 요소를 의미한다.

 

HTML 요소는 렌더링 엔진에 의해 파싱되어 DOM을 구성하는 요소 노드 객체로 반환된다.

 

이때 HTML 요소의 속성은 속성 노드로, 텍스트 콘텐츠는 텍스트 노드로 변환된다.

 

 

 

HTML 요소의 콘텐츠 영역(시작태그와 종료 태그 사이) 에는 텍스트 뿐만 아니라, 다른 HTML 요소도 포함할 수 있다.

이때 HTML 요소 간에는 중첩 관계에 의해 계층적인 부자 관계가 형성된다.

이러한 HTML 요소간의 부자 관계를 반영하여 HTML 문서의 구성요소인 HTML 요소를 객체화한 모든 노드 객체들을

트리 자료 구조로 구성한다.

 

트리 자료구조

: 노드들의 계층 구조로 이뤄진다. 즉, 트리 자료구조는 부모 노드와 자식 노드로 구성되어 노드간의 계층적 구조 (부자,형제)를 표현하는 비선형 자료구조를 말한다.

 

노드 객체들로 구성된 트리 자료구조를 DOM이라고 한다.

 

 

 

 

 

39.1.2 노드 객체의 타입 

 

 

 

1) 문서노드

 

문서 노드는 DOM 트리의 최상위에 존재하는 루트 노드로서 document 객체를 가리킨다.

document객체는 DOM 트리의 루트 노드이므로 DOM 트리의 노드들에 접근하기 위한 진입점 역할을 담당한다.

즉, 요소, 어트리뷰트, 텍스트 노드에 접근하려면 문서 노드를 통해야한다.

 

2) 요소노드

 

요소노드는 HTML 요소를 가리키는 객체다.

요소노드는 HTML 요소간의 중첩에 의해 부자관계를 가지며, 이 부자관계를 통해 정보를 구조화한다.

 

3) 어트리뷰트 노드

 

어트리뷰트 노드는 HTML요소의 어트리뷰트를 가리키는 객체다.

어트리뷰트 노드는 부모 노드와 연결되어 있지 않고, 요소 노드에만 연결되어있다. 

 

4) 텍스트노드

 

텍스트 노드는 HTML요소의 텍스트를 가리키는 객체다. 

요소 노드가 문서의 구조를 표현한다면, 텍스트 노드는 문서의 정보를 표현한다.

텍스트 노드는 요소 노드의 자식 노드이며, 자식 노드를 가질 수 없는 리프 노드이다.

즉, 텍스트 노드는 DOM트리의 최종단이다.

 

 

 

 

39.1.3 노드 객체의 상속 구조

 

노드 객체도 자바스크립트 객체이므로 프로토타입에 의한 상속 구조를 갖는다. 

 

 

위 그림과 같이 모든 노드 객체는 Object, EventTarget, Node 인터페이스를 상속받는다.

 

 

저자님 왈...

 

"중요한 것은 DOM API, 즉 DOM이 제공하는 프로퍼티와 메서드를 사용하여 노드에 접근하고

HTML 구조나 내용 또는 스타일링 등을 동적으로 변경하는 방법을 익히는 것이다."

 

 

 

 

39.2 요소 노드 취득

 

HTML의 구조나 내용 또는 스타일 등을 동적으로 조작하려면 먼저 요소 노드를 취득해야한다.

이는 텍스트 노드나 어트리뷰트 노드를 조작하고자 할 때도 마찬가지이다.

어트리뷰트 노드는 요소 노드와 연결되어 있고, 텍스트 노드는 요소 노드의 자식 노드이기 때문이다. 

 

요소 노드의 취득은 HTML 요소를 조작하는 시작점이다.

이를 위해 DOM은 요소 노드를 취득할 수 있는 다양한 메서드를 제공한다. 

 

 

1) id를 이용한 요소 노드 취득

 

document.getElementById 메서드는 인수로 전달한 id값을 갖는 하나의 요소 노드를 탐색하여 반환한다.

 

<!DOCTYPE html>
<html>
  <body>
    <ul>
      <li id="apple">Apple</li>
      <li id="banana">Banana</li>
      <li id="orange">Orange</li>
    </ul>
    <script>
      // id 값이 'banana'인 요소 노드를 탐색하여 반환한다.
      // 두 번째 li 요소가 파싱되어 생성된 요소 노드가 반환된다.
      const $elem = document.getElementById('banana');

      // 취득한 요소 노드의 style.color 프로퍼티 값을 변경한다.
      $elem.style.color = 'red';
    </script>
  </body>
</html>

 

id값은 HTML문서내에서 유일한 값이어야 하며, class 어트리뷰트와 달리 공백 문자로 구분하여 여러개 값을 가질 수 없다.

 

만약 인수로 전달된 id 값을 갖는 HTML요소가 존재하지 않는 경우 getElementById 메서드는 null을 반환한다.

<!DOCTYPE html>
<html>
  <body>
    <ul>
      <li id="apple">Apple</li>
      <li id="banana">Banana</li>
      <li id="orange">Orange</li>
    </ul>
    <script>
      // id 값이 'grape'인 요소 노드를 탐색하여 반환한다. null이 반환된다.
      const $elem = document.getElementById('grape');

      // 취득한 요소 노드의 style.color 프로퍼티 값을 변경한다.
      $elem.style.color = 'red';
      // -> TypeError: Cannot read property 'style' of null
    </script>
  </body>
</html>

 

2) 태그 이름을 이용한 요소 노드 취득

 

 

document.getElementsByTagName 메서드는 인수로 전달한 태그 이름을 갖는 모든 요소 노드들을 탐색하여 변환한다.

메서드 이름에 포함된 Elements가 복수인 것에서 알 수 있듯이,

이 메서드는 여러개의 요소 노드 객체를 갖는 DOM 컬렉션 객체인HTMLCollection 객체를 반환한다.

 

<!DOCTYPE html>
<html>
  <body>
    <ul>
      <li id="apple">Apple</li>
      <li id="banana">Banana</li>
      <li id="orange">Orange</li>
    </ul>
    <script>
      // 태그 이름이 li인 요소 노드를 모두 탐색하여 반환한다.
      // 탐색된 요소 노드들은 HTMLCollection 객체에 담겨 반환된다.
      // HTMLCollection 객체는 유사 배열 객체이면서 이터러블이다.
      const $elems = document.getElementsByTagName('li');

      // 취득한 모든 요소 노드의 style.color 프로퍼티 값을 변경한다.
      // HTMLCollection 객체를 배열로 변환하여 순회하며 color 프로퍼티 값을 변경한다.
      [...$elems].forEach(elem => { elem.style.color = 'red'; });
    </script>
  </body>
</html>

 

Q. HTMLCollection이란? 

함수는 하나의 값만 반환할 수 있으므로 여러개의 값을 반환하려면 배열이나 객체와 같은 자료구조에 담아 변환해야한다.

getElementsByTagName 메서드가 반환하는 DOM 컬렉션 객체인

HTMLCollection 객체는 유사 배열 객체이면서, 이터러블이다.

 

    

만약 인수로 전달된 태그 이름을 갖는 요소가 존재하지 않을 경우,

getElementsByTagName 메서드는 빈 HTMLCollection을 반환한다.

 

 

3) class를 사용한 요소 노드 취득 

 

document.getElementsByClassName 메서드는 인수로 전달한 class값을 갖는 모든 요소들을 탐색하여 반환한다.

getElementsByTagName와 마찬가지로 여러개의 요소 노드 객체를 갖는 DOM 컬렉션 객체인 HTMLCollection 객체를 반환한다. 

 

<!DOCTYPE html>
<html>
  <body>
    <ul id="fruits">
      <li class="apple">Apple</li>
      <li class="banana">Banana</li>
      <li class="orange">Orange</li>
    </ul>
    <div class="banana">Banana</div>
    <script>
      // DOM 전체에서 class 값이 'banana'인 요소 노드를 모두 탐색하여 반환한다.
      const $bananasFromDocument = document.getElementsByClassName('banana');
      console.log($bananasFromDocument); // HTMLCollection(2) [li.banana, div.banana]

      // #fruits 요소의 자손 노드 중에서 class 값이 'banana'인 요소 노드를 모두 탐색하여 반환한다.
      const $fruits = document.getElementById('fruits');
      const $bananasFromFruits = $fruits.getElementsByClassName('banana');

      console.log($bananasFromFruits); // HTMLCollection [li.banana]
    </script>
  </body>
</html>

 

만약 인수로 전달된 태그 이름을 갖는 요소가 존재하지 않을 경우,

getElementsByClassName  메서드는 빈 HTMLCollection을 반환한다.

 

 

4) CSS 선택자를 이용한 요소 노드 취득 

 

4-1) document.querySelector

: 인수로 전달한 CSS 선택자를 만족시키는 하나의 요소 노드를 탐색하여 반환

 

  • 인수로 전달한 CSS 선택자를 만족시키는 요소 노드가 여러개인 경우 첫번 째 요소 노드만 반환한다.
  • 인수로 전달된 CSS 선택자를 만족시키는 요소가 존재하지 않는 경우 null을 반환한다.
  • 인수로 전달한 CSS 선택자가 문법에 맞지 않을 경우 DOMException 에러가 발생한다.
<!DOCTYPE html>
<html>
  <body>
    <ul>
      <li class="apple">Apple</li>
      <li class="banana">Banana</li>
      <li class="orange">Orange</li>
    </ul>
    <script>
      // class 어트리뷰트 값이 'banana'인 첫 번째 요소 노드를 탐색하여 반환한다.
      const $elem = document.querySelector('.banana');

      // 취득한 요소 노드의 style.color 프로퍼티 값을 변경한다.
      $elem.style.color = 'red';
    </script>
  </body>
</html>

 

 

4-2)document.querySelectorAll

: 인수로 전달한 CSS 선택자를 만족시키는 모든 요소 노드를 탐색하여 반환한다

querySelectorAll 메서드는 여러 개의 요소 노드 객체를 갖는 DOM 컬렉션 객체인 NodeList 객체를 반환한다.

NodeList 객체는 유사 배열 객체이면서 이터러블이다.

 

<!DOCTYPE html>
<html>
  <body>
    <ul>
      <li class="apple">Apple</li>
      <li class="banana">Banana</li>
      <li class="orange">Orange</li>
    </ul>
    <script>
      // ul 요소의 자식 요소인 li 요소를 모두 탐색하여 반환한다.
      const $elems = document.querySelectorAll('ul > li');
      // 취득한 요소 노드들은 NodeList 객체에 담겨 반환된다.
      console.log($elems); // NodeList(3) [li.apple, li.banana, li.orange]

      // 취득한 모든 요소 노드의 style.color 프로퍼티 값을 변경한다.
      // NodeList는 forEach 메서드를 제공한다.
      $elems.forEach(elem => { elem.style.color = 'red'; });
    </script>
  </body>
</html>

 

 

 

5) 특정 요소를 취득할 수 있는지 확인

 

 

Element.prototype.matches 메서드는 인수로 전달한 CSS 선택자를 통해 특정 요소 노드를 취득 할 수 있는지 확인한다.

 

<!DOCTYPE html>
<html>
  <body>
    <ul id="fruits">
      <li class="apple">Apple</li>
      <li class="banana">Banana</li>
      <li class="orange">Orange</li>
    </ul>
  </body>
  <script>
    const $apple = document.querySelector('.apple');

    // $apple 노드는 '#fruits > li.apple'로 취득할 수 있다.
    console.log($apple.matches('#fruits > li.apple'));  // true

    // $apple 노드는 '#fruits > li.banana'로 취득할 수 없다.
    console.log($apple.matches('#fruits > li.banana')); // false
  </script>
</html>

 

★39.2 요소 노드 취득 결론

 

id 어트리뷰트가 있는 요소 노드를 취득하는 경우에는 getElementById 메서드를 사용하고 

그 외에 경우에는 querySelector, querySelectorAll 메서드를 사용하는 것을 권장한다.

 

 

 

39.3 노드 탐색

 

요소 노드를 취득한 다음, 취득한 요소 노드를 기점으로 DOM 트리의 노드를 옮겨 다니며 부모, 형제, 자식 노드 등을 탐색해야 할 때가

있다. 

<ul id="fruits">
  <li class="apple">Apple</li>
  <li class="banana">Banana</li>
  <li class="orange">Orange</li>
</ul>

먼저 ul#fruits 요소 노드를 취득한 다음, 자식 노드를 모두 탐색하거나 자식 노드 중 하나만 탐색할 수 있다. 

혹은 li:banana 요소 노드를 취한 다음에, 형제 노드를 탐색하거나 부모 노드를 탐색할 수도 있다.

 

이처럼 DOM 트리 상의 노드를 탐색할 수 있도록 Node, Element 인터페이스는 트리 탐색 프로퍼티를 제공한다.

 

 

1) 자식 노드 탐색

 

Node.prototype.childNodes 자식 노드를 모두 탐색하여 DOM 컬렉션 객체인 NodeList 에 담아 반환한다.
요소 노드 뿐 아니라, 텍스트 노드도 포함될 수 있다.
Element.prototype.children 자식 노드 중에서 요소 노드만 모두 탐색하여 DOM 컬렉션 객체인 
HTMLCollection에 담아 반환한다.
텍스트 노드는 포함되지 않는다.
Node.prototype.firstChild 첫번째 자식 노드를 반환한다. 텍스트 노드 or 요소 노드 반환
Node.prototype.lastChild 마지막 자식 노드를 반환한다. 텍스트 노드 or 요소 노드 반환
Element.prototype.firstElementChild 첫번째 자식 노드를 반환한다. only 요소 노드만 반환
Element.prototype.lastElementChild 마지막 자식 노드를 반환한다. only 요소 노드만 반환
<!DOCTYPE html>
<html>
  <body>
    <ul id="fruits">
      <li class="apple">Apple</li>
      <li class="banana">Banana</li>
      <li class="orange">Orange</li>
    </ul>
  </body>
  <script>
    // 노드 탐색의 기점이 되는 #fruits 요소 노드를 취득한다.
    const $fruits = document.getElementById('fruits');

    // #fruits 요소의 모든 자식 노드를 탐색한다.
    // childNodes 프로퍼티가 반환한 NodeList에는 요소 노드뿐만 아니라 텍스트 노드도 포함되어 있다.
    console.log($fruits.childNodes);
    // NodeList(7) [text, li.apple, text, li.banana, text, li.orange, text]

    // #fruits 요소의 모든 자식 노드를 탐색한다.
    // children 프로퍼티가 반환한 HTMLCollection에는 요소 노드만 포함되어 있다.
    console.log($fruits.children);
    // HTMLCollection(3) [li.apple, li.banana, li.orange]

    // #fruits 요소의 첫 번째 자식 노드를 탐색한다.
    // firstChild 프로퍼티는 텍스트 노드를 반환할 수도 있다.
    console.log($fruits.firstChild); // #text

    // #fruits 요소의 마지막 자식 노드를 탐색한다.
    // lastChild 프로퍼티는 텍스트 노드를 반환할 수도 있다.
    console.log($fruits.lastChild); // #text

    // #fruits 요소의 첫 번째 자식 노드를 탐색한다.
    // firstElementChild 프로퍼티는 요소 노드만 반환한다.
    console.log($fruits.firstElementChild); // li.apple

    // #fruits 요소의 마지막 자식 노드를 탐색한다.
    // lastElementChild 프로퍼티는 요소 노드만 반환한다.
    console.log($fruits.lastElementChild); // li.orange
  </script>
</html>

 

 

 

2)자식 노드 존재 확인

 

Node.prototype.hasChildNodes 메서드 사용 

자식 노드가 존재하면 true, 자식 노드가 존재 하지 않으면 false를 반환한다.

<!DOCTYPE html>
<html>
  <body>
    <ul id="fruits">
    </ul>
  </body>
  <script>
    // 노드 탐색의 기점이 되는 #fruits 요소 노드를 취득한다.
    const $fruits = document.getElementById('fruits');

    // #fruits 요소에 자식 노드가 존재하는지 확인한다.
    // hasChildNodes 메서드는 텍스트 노드를 포함하여 자식 노드의 존재를 확인한다.
    console.log($fruits.hasChildNodes()); // true
  </script>
</html>

 

3) 요소 노드의 텍스트 노드 탐색

 

firstChild 프로퍼티는 첫번째 자식 노드를 반환한다. firstChild 프로퍼티가 반환한 노드는 텍스트 노드이거나 요소 노드이다.

<!DOCTYPE html>
<html>
<body>
  <div id="foo">Hello</div>
  <script>
    // 요소 노드의 텍스트 노드는 firstChild 프로퍼티로 접근할 수 있다.
    console.log(document.getElementById('foo').firstChild); // #text
  </script>
</body>
</html>

 

4) 부모 노드 탐색

 

Node.prototype.parentNode 프로퍼티를 사용한다. 

<!DOCTYPE html>
<html>
  <body>
    <ul id="fruits">
      <li class="apple">Apple</li>
      <li class="banana">Banana</li>
      <li class="orange">Orange</li>
    </ul>
  </body>
  <script>
    // 노드 탐색의 기점이 되는 .banana 요소 노드를 취득한다.
    const $banana = document.querySelector('.banana');

    // .banana 요소 노드의 부모 노드를 탐색한다.
    console.log($banana.parentNode); // ul#fruits
  </script>
</html>

 

5) 형제 노드 탐색 

 

Node.prototype.previousSibling 자신의 이전 형제 노드를 탐색하여 반환. 요소 노드 or 텍스트 노드 반환
Node.prototype.nextSibling 자신의 다음 형제 노드를 탐색하여 반환. 요소 노드 or 텍스트 노드 반환
Element.prototype.previousElementSibling 자신의 이전 형제 노드를 탐색하여 반환. only 요소 노드 반환
Element.prototype.nextElementSibling 자신의 다음 형제 노드를 탐색하여 반환. only 요소 노드 반환

 

<!DOCTYPE html>
<html>
  <body>
    <ul id="fruits">
      <li class="apple">Apple</li>
      <li class="banana">Banana</li>
      <li class="orange">Orange</li>
    </ul>
  </body>
  <script>
    // 노드 탐색의 기점이 되는 #fruits 요소 노드를 취득한다.
    const $fruits = document.getElementById('fruits');

    // #fruits 요소의 첫 번째 자식 노드를 탐색한다.
    // firstChild 프로퍼티는 요소 노드뿐만 아니라 텍스트 노드를 반환할 수도 있다.
    const { firstChild } = $fruits;
    console.log(firstChild); // #text

    // #fruits 요소의 첫 번째 자식 노드(텍스트 노드)의 다음 형제 노드를 탐색한다.
    // nextSibling 프로퍼티는 요소 노드뿐만 아니라 텍스트 노드를 반환할 수도 있다.
    const { nextSibling } = firstChild;
    console.log(nextSibling); // li.apple

    // li.apple 요소의 이전 형제 노드를 탐색한다.
    // previousSibling 프로퍼티는 요소 노드뿐만 아니라 텍스트 노드를 반환할 수도 있다.
    const { previousSibling } = nextSibling;
    console.log(previousSibling); // #text

    // #fruits 요소의 첫 번째 자식 요소 노드를 탐색한다.
    // firstElementChild 프로퍼티는 요소 노드만 반환한다.
    const { firstElementChild } = $fruits;
    console.log(firstElementChild); // li.apple

    // #fruits 요소의 첫 번째 자식 요소 노드(li.apple)의 다음 형제 노드를 탐색한다.
    // nextElementSibling 프로퍼티는 요소 노드만 반환한다.
    const { nextElementSibling } = firstElementChild;
    console.log(nextElementSibling); // li.banana

    // li.banana 요소의 이전 형제 요소 노드를 탐색한다.
    // previousElementSibling 프로퍼티는 요소 노드만 반환한다.
    const { previousElementSibling } = nextElementSibling;
    console.log(previousElementSibling); // li.apple
  </script>
</html>

 

이미지 출처 :

HTML요소 이미지- https://happyfridaymorning.co.kr/52  

 

HTML5 기본용어 익히기 (코드, 태그, 속성, 요소란 무엇인가?)

오늘은 HTML5를 공부하기에 앞서 HTML5의 기본적인 용어를 알아보도록 하겠습니다. 책에서는 보통 한글로 용어들을 표현하고 있지만, 실무에서는 보통 영어로 이야기한다고 합니다. 그래서 저도

happyfridaymorning.co.kr

노드 관계 이미지 - https://velog.io/@hyowon_lee/JavaScript-textContent-%EC%99%80-innerText

 

[JavaScript] textContent 와 innerText

특정 요소를 숨기기 위해 display: none 을 사용하면 리플로우가 일어난다. 리플로우 없는 다른 방법이 없을까 생각하다가 textContent는 리플로우가 발생하지 않는다는 생각이 났다. 찾아보다가 textCon

velog.io