본문 바로가기
Javascript study

[JS] DOM 탐색하기

by 카누가 좋아요 2023. 8. 17.

📌 참고 사이트

https://ko.javascript.info/dom-navigation

 

DOM 탐색하기

 

ko.javascript.info

 

 

 

📋 DOM 접근하기

요소에 어떤 작업을 하고자 할 때에는 우선 조작하고자 하는 DOM 객체에 접근하는 것부터 선행되어야 한다.

DOM에 수행하는 모든 연산은 진입점 역할을 하는 document 객체에서 시작한다. 

 

DOM 노드들의 관계

 

 

📍 트리 상단의 documentElement와 body

➡️ <html>

document.documentElement를 통해 접근 가능하다.

html은 document를 제외하고 DOM 트리의 가장 위에 위치한다.

 

➡️ <body>

document.body는 <body> 요소에 해당하는 DOM 노드이다.

 

➡️ <head>

document.head로 접근이 가능하다.

 

❓ DOM에서의 null

DOM에서의 null은 해당하는 노드가 없음을 나타낸다.

다음과 같은 코드에서 null의 결과를 볼 수 있다.

 

<html>
<head>
  <script>
    alert(document.body); // null, 아직 <body>에 해당하는 노드가 생성되지 않았음
  </script>
</head>
<body>
  <script>
    alert(document.body); // [object HTMLBodyElement], 지금은 노드가 존재하므로 읽을 수 있음
  </script>
</body>
</html>

 

<head> 안에서의 alert(document.body)에서는 아직 <body>가 있는 곳까지 브라우저가 읽지 않았기 때문에 접근이 불가하므로 null이 뜨게 된다.

but <body> 안에서의 alert(document.body)에서는 브라우저가 <body>를 읽고 노드가 생성된 상태이기 때문에 접근이 가능하여 null이 뜨지 않게 된다.

 

 

 

📋 자식 노드 탐색하기

다음과 같은 HTML 코드가 있다고 해 보자.

 

<html>
<body>
  <div>시작</div>
  <ul>
    <li>
      <b>항목</b>
    </li>
  </ul>
</body>
</html>

 

❓ 자식 노드 (child node)

자식 노드는 부모 노드의 바로 아래에서 중첩 관계를 만든다.

위의 코드를 보면 <head>와 <body>는 <html>의 자식 노드인 것을 볼 수 있다.

 

❓ 후손 노드 (descendants)

어떤 노드의 자식 노드와 자식 노드의 모든 자식 노드들이 후손 노드가 된다.

위의 코드에서 <body>의 후손 노드는 자식 노드인 <div>, <ul> 뿐만 아니라 <li>와 <b> 같은 더 안쪽에 있는 요소들도 모두 될 수 있다.

 

 

📍 childNodes

chilsNodes 컬렉션은 텍스트 노드를 포함모든 자식 노드를 담고 있다.

element.childNodes의 형식으로 사용하면 된다.

 

위의 예시에서 <body>의 모든 자식 요소들의 태그명을 출력하려면 다음과 같이 하면 된다.

 

document.body.childNodes.forEach((node) => console.log(node.tagName));
// undefined, DIV, undefined, UL, undefined
// undefined는 모두 텍스트 노드(공백, 줄바꿈)에 해당한다.

 

 

📍 firstChild, lastChild

firstChildlastChild 프로퍼티를 이용하면 각각 첫 번째, 마지막 자식 노드에 빠르게 접근할 수 있다.

즉, element.firstChild === element.childNodes[0], 그리고 element.lastChild === element.childNodes[element.childNodes.length - 1] 이라고 볼 수 있다.

 

💡 자식 노드의 존재 여부를 검사할 때는 element.hasChildNodes()를 사용할 수 있다.

 

 

 

📋 DOM 컬렉션

위에서 본 childNodes는 배열 같지만 사실은 배열이 아닌 반복 가능한(iterable) 유사 배열 객체인 컬렉션(collection)이다.

 

컬렉션은 다음과 같은 특징을 가진다.

 

➡️ for ... of를 사용할 수 있다.

반복 가능한 객체이기 때문에 Symbol.iterator 프로퍼티가 구현되어 있어 for ...of 문을 사용하는 것이 가능하다. (비슷한 forEach도 사용 가능하다.)

바로 위에서 본 html을 예시로 사용해 보겠다.

 

for (let node of document.body.childNodes) {
  console.log(node);
}
// #text, <div>시작</div>, #text, <ul>…</ul>, #text

 

for ... of 문이 제대로 작동하여 <body>의 모든 자식 노드들을 출력하는 것을 볼 수 있다.

 

❗ 이때 for ... in 반복문을 사용하려 하지 말자. 

for ... in 반복문은 객체의 모든 열거 가능한 프로퍼티를 순회하기 때문에 거의 사용되지 않는 프로퍼티까지 전부 순회할 수가 있다.

 

➡️ 배열이 아니기 때문에 배열에 사용되는 일부 메서드를 사용할 수 없다.

아래와 같은 코드로는 원하는 결과를 얻을 수 없다.

 

console.log(document.body.childNodes.filter);
// undefined (filter 메서드가 없다.)

 

위와 같이 filter 뿐만 아니라 map(유효한 함수가 아니라는 오류를 뱉음.) 같은 것들도 사용 불가이다.

 

❗ DOM 컬렉션은 읽는 것만 가능하다.

DOM 컬렉션읽기 전용으로, childNodes[i] = .... 등과 같은 방식으로 자식 노드를 교체하는 것이 불가하다.

따라서 배열에 사용되는 메서드 사용이 불가하다.

 

 

 

📋 형제와 부모 노드

형제(sibling) 노드같은 부모 노드를 가진 같은 레벨에 있는 노드들을 말한다.

대표적으로 <html>을 공통 부모 노드로 가지는 <head>와 <body>는 형제 노드이다.

 

 

📍 nextSibling, previousSibling

이때 <head>의 바로 다음 형제 노드, 즉 nextSibling은 <body> 노드가 되고, <body>의 바로 이전 형제 노드, 즉 previousSibling은 <head> 노드가 된다. 

이 둘은 프로퍼티로 사용이 가능하다.

 

console.log(document.head.nextSibling);     // <body></body>
console.log(document.body.previousSibling);     // <head></head>

 

 

📍 parentNode

<head>와 <body>의 부모 노드, 즉 parentNode는 <html>이 된다.

이것도 프로퍼티로 사용이 가능하다.

 

console.log(document.head.parentNode);     // <html>...</html>
console.log(document.body.parentNode);     // <html>...</html>

 

 

 

📋 요소 간 이동

childNodes를 이용하면 텍스트 노드, 요소 노드, 주석 노드까지 참조할 수 있는데, 텍스트 노드나 주석 노드는 잘 다루지 않는다.

대부분 요소 노드를 조작하는 작업에 이용된다.

 

요소 간의 관계를 볼 때는 다음의 것들을 기억하자.

 

➡️ children 프로퍼티는 해당 요소의 자식 노드요소 노드만을 가리킨다.

(childNodes는 비요소 노드들을 포함하여 값을 반환하지만 children 프로퍼티를 사용하면 비요소 노드들은 모두 제외되고 오로지 요소 노드들만을 얻을 수 있다.)

➡️ firstElementChildlastElementChild 프로퍼티는 각각 첫 번째 자식 요소 노드마지막 자식 요소 노드를 가리킨다.

➡️ previousElementSiblingnextElementSibling은 각각 바로 이전 형제 요소 노드, 바로 다음 형제 요소 노드를 가리킨다.

➡️ parentElement부모 요소 노드를 가리킨다.

 

💡 parentElement는 부모 '요소' 노드를 반환하지만, parentNode는 종류에 상관 없이 부모면 반환한다.

두 프로퍼티를 사용하면 대부분의 경우에서 같은 결과를 볼 수 있지만 다음과 같은 경우에서는 다른 결과를 반환한다.

 

// document.documentElement는 html을 의미한다.
console.log(document.documentElement.parentNode);     // document
console.log(document.documentElement.parentElement);     // null

 

html의 부모는 document이다.

그런데 document는 '요소' 노드가 아니라 '문서' 노드이기 때문에 그냥 부모 노드면 반환하는 parentNode에서는 document가 출력되지만, 부모 '요소' 노드를 반환하는 parentElement에서는 null이 출력된다.

 

따라서 parentElement는 상위 객체인 <html>까지 올라가고 싶은데, document 까지는 도달하고 싶지 않을 때 사용하면 유용하다.

 

 

 

✏️ 과제

📍 자식 DOM

아래 페이지를 살펴보자.

 

<html>
<body>
  <div>사용자:</div>
  <ul>
    <li>John</li>
    <li>Pete</li>
  </ul>
</body>
</html>

 

아래 DOM 노드에 접근할 방법을 최소 한 가지 이상씩 생각해 보자.

➡️ <div> DOM 노드

➡️ <ul> DOM 노드

➡️ 두 번째 <li> (Pete)

 

 

🅰️

 

💻 <div>에 접근하기

 

// 1. body의 첫 번째 자식으로 접근하기
document.body.firstElementChild;     // 그냥 firstChild는 text(공백)임에 주의!
document.body.children[0];
document.body.childNodes[1];     // 첫 번째 노드인 공백 건너뛰고 두 번째 노드를 가져옴.

// 2. ul의 이전 형제로 접근하기
document.body.children[1].previousElementSibling;

 

💻 <ul>에 접근하기

 

// 1. body의 두 번째 자식으로 접근하기
document.body.children[1];
document.body.lastElementChild;

// 2. div의 바로 다음 형제로 접근하기
document.body.children[0].nextElementSibling;

 

💻 두 번째 <li>에 접근하기

 

// ul의 2번째 자식으로 접근하기
document.body.children[1].children[1];
document.body.lastElementChild.lastElementChild;

 

 

📍 형제 노드에 관한 질문

임의의 DOM 요소 노드 element가 있다고 가정해 보자. (element의 자식 노드는 항상 존재한다고 가정)

1️⃣ element.lastChild.nextSibling은 항상 null일까?

2️⃣ element.children[0].previousSibling은 항상 null일까?

 

🅰️

1️⃣ 맞다. element.lastChild는 항상 마지막 노드(요소 노드 뿐만 아니라 다른 노드 통틀어서)이므로 그 다음 형제 노드는 존재하지 않는다.

2️⃣ 아니다. element.children[0]은 첫 번째 자식 '요소' 노드를 나타내므로 요소 노드가 아닌 노드(텍스트 노드 같은 것)가 앞에 위치해 있다면 그것이 이전 형제 노드가 되어 반환 가능하다.

댓글