웹 프론트엔드

자바스크립트5 - DOM(Document Object Model)

토리쟁이 2024. 1. 14. 01:45

 

 

이번 포스팅에서는 DOM에 대해 공부해 보고자 한다.

 


 

 

 

DOM(Document Object Model)

  • 문서 객체 모델
  • HTML 문서 요소의 집합
  • HTML 문서는 각각의 node와 object의 집합으로 문서를 표현하는데, 이러한 각각의 node 나 object에 접근하여 문서 구조, 스타일, 내용 등을 변경하고자 할 때 사용하는 객체
  • 즉, 웹 페이지의 콘텐츠 및 구조, 그리고 스타일 요소를 구조화시켜 표현하여 프로그래밍 언어가 해당 문서에 접근하여 읽고 조작할 수 있도록 API를 제공하는 일종의 인터페이스
  • DOM은 웹 페이지, 즉 HTML 문서를 계층적 구조와 정보로 표현하며, 이를 제어할 수 있는 속성과 메서드를 제공하는 트리 자료구조

 

 

 

원하는 요소를 변경시키기 위해서는 그 대상 요소를 가지고 올 수 있어야 한다.

이를 위한 여러 가지 요소 선택 방법들이 존재한다.

 

예제를 위한 html 코드는 다음과 같다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DOM</title>
</head>
<body>
    <div class="container">
        <div class="box" id="green">1</div>
        <div class="box" id="red">2</div>
        <div class="box">3</div>
        <div class="box">4</div>
        <div class="box">5</div>
    </div>
    <input type="text" name="userName">
    <input type="text" name="userName">

    <!--요소 다루기-->
    <div id="parent">
        Text
        <div id="two">div 두번째 Text</div>
        <div>div 세번째 Text</div>
    </div>
</body>
</html>

 

 

요소 선택

  •  querySelector("요소 선택자"): 요소 선택자와 일치하는 문서 내 첫 번째 요소 반환
    • class: .
    • id: #
    • 태그: 태그명
  • querySelectorAll( "요소 선택자"): 요소 선택자와 일치하는 문서 내 모든 요소 반환
  • getElementById("ID 이름"): 해당 id를 가지고 있는 요소를 반환(맨 처음 등장하는 요소만 반환하며, id값은 고유하게 작성해야 하므로 하나만 있는 것이 당-연)
  • getElementsByClassName("class 이름"): 해당 클래스 이름을 가진 요소를 반환
    • 반환값: HtmlCollection
  • getElementsByTagName('태그명'): 해당 태그인 모든 요소를 반환
    • 반환값: HtmlCollection
  • getElementsByName('name명'): 해당 name명과 같은 모든 요소를 반환
    • 반환값: NodeList
    • 주로 input의 속성으로 자주 사용되는 name=""을 이용

 

 

위의 설명을 보면, 해당 함수의 반환값이 HtmlCollection인 경우와 NodeList인 경우가 각각 있다는 것을 알 수 있다.

그 두 경우의 차이점은 다음과 같다.

 

 HtmlCollection VS NodeList

  • HtmlCollection 객체는 태그 요소만 포함하는 반면, NodeList 객체는 태그와 텍스트 노드 등을 모두 포함한다.
  • HtmlCollection 객체는 자식 요소 노드에 접근할 때, 인덱스 방식 & namedItem 방식을 모두 사용할 수 있지만, NodeList 객체는 인덱스로만 접근이 가능하다.
  • HtmlCollection 객체는 동적이고, NodeList 객체는 정적이다.(아래 설명 참고)
  • HtmlCollection 객체는 DOM을 다루면서 새로운 요소가 추가됐을 때, 추가된 요소도 가져올 수 있지만, NodeList 객체는 새로 추가된 요소를 가져올 수 없다.
  • HtmlCollection 객체는 배열과 유사하지만, 실제 배열이 아니다. but,  NodeList 객체는 배열이기 때문에 배열만의 사용 가능한 메소드(forEach 등..)를 사용할 수 있다.

 

let parent = document.getElementById('parent');
        let parentChildren = parent.children; //HtmlCollection 반환
        let parentChildNodes = parent.childNodes; // NodeList 반환

        console.log('children:', parentChildren);
        console.log('children:', parentChildren.namedItem('two')); // NodeList는 안되지만, HtmlCollection은 이렇게 접근이 가능함
        console.log('ChildNodes:', parentChildNodes);
        
        
// 자식 추가
// 추가할 부모 선택 -> 추가할 자식 생성 -> 자식 추가
// 위에서 추가할 부모를 선택했으므로, 추가할 자식 생성을 해보자
let child1 = document.createElement('div'); // <div></div> 생성
child1.innerText = '네번째 Text'; // 태그 안 내용
parent.appendChild(child1); // 자식 추가

 

 

 

 

요소 다루기

 

태그 내부 내용 변경

  • 아래의 3가지 방법들은 모두 태그 내에 들어갈 문자열을 지정한다는 공통점이 있지만, 각각의 차이점도 존재한다.
  • innerText: 문자열을 넣되, 해당 문자열의 공백이 제거됨
  • innerContent: 문자열을 넣되, 해당 문자열의 공백이 제거되지 X
  • innerHTML: HTML 요소를 넣는 것 (→ 태그를 작성하면 반영되어 나타남)
let two = document.getElementById('two');
        //two.innerHTML = '<strong>강조</strong>';
        //two.innerHTML = '강조'; // 기존에 있던 값들이 없어지므로 윗줄 소용x
        two.innerText = '     textContent<strong>강조</strong>   @   !!!!!!! **      ';
        two.textContent = `     textContent<strong>강조</strong>.   
        //@   !!!!!!! **    `
        
        console.log(two.innerText); // 공백 제거
        console.log(two.textContent); // 공백 제거 x

 

 

 

 

클래스 관련 메소드 classList~

  • 선택 요소에 class를 추가/삭제/존재여부 확인하는 메소드들
  • 요소.classList.add(): 선택 요소에 클래스 추가
  • 요소. classList.remove(): 선택 요소에 클래스 삭제
  • 요소. classList.contains(): 선택 요소에 클래스 유무 확인 → true/false 반환
  • 요소. classList.toggle(): 클래스가 있으면 제거, 없으면 추가
greenBox.classList.add('box1'); // 클래스 추가
greenBox.classList.remove('box1'); // 클래스 삭제
console.log(greenBox.classList.contains('box1')); // 클래스 여부 확인
greenBox.classList.toggle('d-none'); // 해당 클래스가 있으면 제거 없으면 추가

// toggle을 이용한 예제 문제
// 현재 문제에서는 완료한 일이 todo 클래스로 되어 있고, 해야할 일이 done 클래스로 되어 있다.
// 바꿔치기 ㄱㄱ


let lists = document.getElementsByTagName('li');
for (let li of lists) {
    li.classList.toggle('done')
    li.classList.toggle('todo')
    console.log(li)
}

 

 

 

속성 변경 메소드

  • 요소.setAtrribute("속성명", "지정할 속성")
  • 선택요소.해당 요소가 갖는 속성 = "원하는 속성값"
// 속성 변경 메소드
let inputE1 = document.getElementById('userName');
inputE1.setAttribute('placeholder', 'name');
inputE1.placeholder = 'name'; // 위의 코드와 동일

inputE1.style = 'color:red';
inputE1.style.color = 'red'; // 위의 코드와 동일

 

 

 

 

다른 노드에 접근하기

  • 특정 노드를 선택한 후, 해당 노드의 형제/부모/자식 노드에 접근 가능
  • 요소.children
  • 요소.parentNode: 특정 노드의 부모 요소
  • 요소.previousElementSibling: 특정 노드의 이전 형제 '요소' 반환 
  • 요소.previousSibling: 특정 노드의 이전 형제 반환(위 코드와 동일한데 차이점이 있음 말로 설명하기 어려우니 실습 코드 참고할 것)
  • 요소.nextElementSibling: 특정 요소의 다음 형제 '요소' 반환
  • 요소.nextSibling: 특정 요소의 다음 형제 반환

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DOM 다른 노드 접근하기</title>
</head>
<body>
    <div class="container">
        <div class="box" id="green">1</div>
        previous 테스트
        <!--주석 previous 테스트--> 
        <div class="box" id="red">2</div>
        next 테스트
        <div class="box">3</div>
        <div class="box">4</div>
        <div class="box">5</div>
    </div>
    <button onclick="getParent()">getParent</button>
    <button onclick="pElSibling()">pElSibling</button>
    <button onclick="pNoSibling()">pNoSibling</button>
    <button onclick="nElsibling()">nElsibling</button>
    <button onclick="nNosibling()">nNosibling</button>

    <script>
        let container = document.getElementById('container');
        let greenBox = document.getElementById('green');
        let redBox = document.getElementById('red');

        // parentNode: 부모 요소를 가져온다
        function getParent()
        {
            console.log(greenBox.parentNode);
        }

        // previousElementSibling vs previousSibling 차이점 공부하기

        // 이전 형제 '요소' 반환
        function pElSibling()
        {
            console.log(redBox.previousElementSibling); //<div class="box" id="green">1</div>
        }

        // 바로 앞에 있는 형제 반환(dom tree 기준)
        // 앞에 있는 것이 주석이거나 비어있으면 #text라고 뜸
        function pNoSibling(){
            console.log(redBox.previousSibling); //#text
        }

        // 다음 형제 '요소' 반환
        function nElsibling(){
            console.log(redBox.nextElementSibling); // <div class="box">3</div>
        }

        // 다음 형제 반환
        function nNosibling(){
            console.log(redBox.nextSibling); //"next 테스트"
        }
    </script>
</body>
</html>

 

 

 

요소의 추가&삭제

  • 요소.createElement('태그명'): 해당 태그를 갖는 요소를 생성 
  • 요소.append() / 요소.appendChild(): 선택된 요소의 맨 뒤의 자식 요소로 추가
  • 요소.prepend(): 선택된 요소의 맨 앞에 자식 요소로 추가
  • 요소.before(): 선택된 요소의 앞에 형제 요소로 추가
  • 요소.after(): 선택된 요소의 바로 뒤에 형제 요소로 추가
  • 요소.remove(): 선택된 요소 삭제
  • 부모요소.removeChild('부모요소의 자식요소'): 선택된 요소의 자식 요소 삭제

예제

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DOM 요소 추가 & 삭제</title>
    <!--js파일 분리해서 사용하는 경우, defer 추가해서 dom요소를 로드 후에 실행!-->
    <script defer>
        let ul = document.querySelector('ul');
        console.log(ul); // null
        // 스크립트는 위에서부터 아래로 읽히기 때문에, 아직 ul 태그가 만들어지지 않아 null 값이 나오게 된다
        // script를 바디 안에 작성하던가
        // script 속성으로 defer을 주던가(body를 읽고 나서,,)
        // 함수 안에 작성하던가
        function createNode(){
            //let ul = document.querySelector('ul');
            //console.log(ul);
            let li = document.createElement('li');
            li.innerText = '새로 추가된 리스트';
            console.log(li);
        }

        function appendNode(){
            let li = document.createElement('li');
            li.innerText = '새로 추가된 리스트';

            // append(): 요소.append() - 선택한 요소의 맨 뒤 자식 요소로 추가
            let ul = document.querySelector('ul');
            ul.append(li);
            ul.append('text');
        }

        function appendChildNode(){
            let li = document.createElement('li');
            li.innerText = '새로 추가된 리스트 append child';

            // appendChild():  요소.appendChild() - 선택한 요소의 맨 뒤 자식 요소로 추가
            let ul = document.querySelector('ul');
            ul.appendChild(li);
            ul.appendChild("text"); // 에러발생 -> appendChild에는 위의 child와 다르게 문자열이 들어가지 못하고 요소 노드만 추가 가능
        }

        function prependNode(){
            let li = document.createElement('li');
            li.innerText = '새로 추가된 리스트 prepend child';
            
            // prepend(): 요소.prepend() - 선택한 요소의 맨 앞 자식 요소로 추가
            let ul = document.querySelector('ul');
            ul.prepend(li);
        }

        function before(){
            // 기존 노드 앞에 형제 요소로 추가
            let standard = document.getElementById("li");
            let li = document.createElement("li");
            li.innerText = 'before';
            standard.before(li);
        }

        function after(){
            // 기존 노드 뒤에 형제 요소로 추가
            let standard = document.getElementById("li");
            let li = document.createElement("li");
            li.innerText = 'after';
            standard.after(li);
        }

        function removeNode(){
            // 내가 선택한 요소 삭제
            let standard = document.getElementById("li");
            // 삭제할 요소에 remove()
            standard.remove();
        }

        function removeChildNode(){
            let ul = document.querySelector('ul');
            let standard = document.getElementById("li");

            // 삭제할 요소의 부모에 removeChild()
            console.log(ul.removeChild(standard));
        }
    </script>
</head>
<body>
    <ul>
        <li>1</li>
        <li>2</li>
        <li id="li">3</li>
        <li>4</li>
        <li>5</li>
    </ul>

    <button onclick="createNode()">createNode</button>
    <button onclick="appendNode()">appendNode</button>
    <button onclick="appendChildNode()">appendChildNode</button>
    <button onclick="prependNode()">prependNode</button>
    <button onclick="before()">before</button>
    <button onclick="after()">after</button>
    <button onclick="removeNode()">removeNode</button>
    <button onclick="removeChildNode()">removeChildNode</button>
</body>
</html>