CodeStates/JavaScript

DOM 추가학습

yeeendy 2023. 5. 2. 11:27

참조 deepdive

 

DOM(Document Object Model)

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

 

트리 자료구조(tree data structure)

✔️노드들의 계층 구조

✔️부모 노드와 자식 노드로 구성되어 노드 간의 계층적 구조를 표현

✔️비선형 자료구조 : 하나의 자료 뒤에 여러 개의 자료가 존재할 수 있는 자료구조 (ex. 트리, 그래프)

<->선형 자료구조 : 하나의 자료 뒤에 하나의 자료만 존재하는 자료구조(ex. 배열, 스택, 큐, 링크드 리스트, 해시 테이블)

✔️하나의 최상위 노드에서 시작 -> 루트 노드 -> 부모 노드X

✔️리프 노드 : 자식 노드가 없는 노드

중요한 노트 타입 4가지

✔️문서 노드 : 최상위에 존재하는 루트 노드, document 객체를 가리킴

✔️요소 노드 : HTML 요소를 가리키는 객체, 문서의 구조를 표현 (ex. div, li, ul ...)

✔️어트리뷰트 노드 : HTML 요소의 어트리뷰트를 가리키는 객체 (ex. id, class, src ...)

✔️텍스트 노드 : HTML 요소의 텍스트를 가리키는 객체, 리프노드, DOM 트리의 최종단

요소 노드 취득

✔️getElementById
-> id 값을 갖는 첫 번째 요소 노드만 반환

-> 요소가 존재하지 않는 경우 null을 반환

✔️getElementsByTagName

-> 태그 이름을 갖는 모든 요소 노드 반환

-> 요소가 존재하지 않는 경우 빈 HTMLCollection 객체를 반환

✔️getElementsByClassName

-> class 값을 갖는 모든 요소 노드 반환

-> 요소가 존재하지 않는 경우 빈 HTMLCollection 객체를 반환

✔️querySelector

-> 하나의 요소 노드들 반환(첫 번째 요소 노드)

-> 요소가 존재하지 않는 경우 null을 반환

✔️querySelectorAll

-> 모든 요소 노드 반환

 

자식 노드 탐색

자식 노드 존재 확인

✔️hasChildNodes()

-> 결과값 true or false

-> 텍스트 노드를 포함하여 자식 노드의 존재를 확인

✔️children.length, childElementCount

-> 텍스트 노드가 아닌 요소 노드가 존재하는지 확인

 

부모 노드 탐색

✔️parentNode

-> 부모 노드가 텍스트 노드인 경우는 없음

 

형제 노드 탐색

노드 정보 취득

요소 노드의 텍스트 조작

✔️nodeValue

->setter와 getter 모두 존재하는 접근자 프로퍼티

->참조와 할당 모두 가능

->텍스트 노드의 값(=텍스트) 반환

->이 외의 노드는 null 반환

->값을 할당하면 텍스트 변경 가능

✔️textContent

->요소 노드의 텍스트와 모든 자손 노드의 텍스트를 모두 취득하거나 변경

->childNodes 프로퍼티가 반환한 모든 노드들의 텍스트 노드 반환

->nodeValue보다 간단하게 코드 작성 가능

✔️innerText

->textContent와 유사한 동작을 하지만 사용 지양

->크로스 사이트 스크립팅 공격에 취약

✔️inserAdjacentHTML

->기존 요소를 제거하지 않으면서 위치를 지정해 새로운 요소를 삽입

->'beforebegin', 'afterbegin', 'beforeend', 'afterend'

->크로스 사이트 스크립팅 공격에 취약

 

노드 생성과 추가

앞 블로그에 다뤘던 내용이라 간단하게 정리

<body>
    <ul id="fruits">
    	<li>Apple</li>
    </ul>
</body>
<script>
    const $fruits = document.getElementById('fruits');
    
    //요소 노드 생성
    const $li = document.createElement('li');
    //텍스트 노드 생성
    const textNode = document.createTextNode('Banana');
    //텍스트 노드를 $li 요소 노드의 자식 노드로 추가
    //$li.textContent = 'Banana';와 동일
    $li.appendChild(textNode);
    //$li 요소 노드를 #fruits 요소 노드의 마지막 자식 노드로 추가
    $fruits.appendChild($li);
</script>

복수의 노드 생성과 추가

//1
//DOM이 3번 변경된다
//비효율적
<body>
    <ul id="fruits"></ul>
</body>
<script>
    const $fruits = document.getElementById('fruits');
    
    ['Apple', 'Banana', 'Orange'].foreach(text => {
        //요소 노드 생성
        const $li = document.createElement('li');
        //텍스트 노드 생성
        const textNode = document.createTextNode(text);
        //텍스트 노드를 $li 요소 노드의 자식 노드로 추가
        $li.appendChild(textNode);
        //$li 요소 노드를 #fruits 요소 노드의 마지막 자식 노드로 추가
        $fruits.appendChild($li);
	});
</script>

//2
//DOM이 1번만 변경
//불필요한 컨테이너 요소가 DOM에 추가되는 부작용
<body>
	<ul id="fruits"></ul>
</body>
<script>
    const $fruits = document.getElementById('fruits');
    
    //컨테이너 요소 노드 생성
    const $container = document.createElement('div');
    
    ['Apple', 'Banana', 'Orange'].foreach(text => {
        //요소 노드 생성
        const $li = document.createElement('li');
        //텍스트 노드 생성
        const textNode = document.createTextNode(text);
        //텍스트 노드를 $li 요소 노드의 자식 노드로 추가
        $li.appendChild(textNode);
        //$li 요소 노드를 컨테이너 요소의 마지막 자식 노드로 추가
        $container.appendChild($li);
	});
    //컨테이너 요소 노드를 $fruits 요소 노드의 마지막 자식 노드로 추가
    $fruits.appendChild($container);
</script>

위에 두 가지 방식은 비효율 적인 방식

✔️DocumentFragment 

->효율적으로 사용할 수 있음

<body>
    <ul id="fruits"></ul>
</body>
<script>
    const $fruits = document.getElementById('fruits');
    
    //DocumentFragment 노드 생성
    const $fragment = document.createDocumentFragment();
    
    ['Apple', 'Banana', 'Orange'].foreach(text => {
        //요소 노드 생성
        const $li = document.createElement('li');
        //텍스트 노드 생성
        const textNode = document.createTextNode(text);
        //텍스트 노드를 $li 요소 노드의 자식 노드로 추가
        $li.appendChild(textNode);
        //$li 요소 노드를 컨테이너 요소의 마지막 자식 노드로 추가
        $fragment.appendChild($li);
	});
    //컨테이너 요소 노드를 $fruits 요소 노드의 마지막 자식 노드로 추가
    $fruits.appendChild($fragment);
</script>

 

지정한 위치에 노드 삽입

✔️inserBefore(newNode, childNode)

->첫 번째 인수로 전달받은 노드를 두 번째 인수로 전달받은 노드 앞에 삽입

<body>
    <ul id="fruits">
    	<li>Apple</li>
        <li>Banana</li>
    </ul>
</body>
<script>
    const $fruits = document.getElementById('fruits');
    
    //요소 노드 생성
    const $li = document.createElement('li');
    //텍스트 노드를 $li 요소 노드의 자식 노드로 추가
    $li.appendChild(document.createTextNode('Orange'));
    //$li 요소 노드를 $fruits 요소 노드의 마지막 자식 요소 앞에 삽입
    $fruits.insertBefore($li, $fruits.lastElementChild);
    // Apple - Orange - Banana
</script>

->두 번째 인수로 전달받은 노드가 null이면 appendChild 메서드처럼 동작

 

노드 이동

✔️appendChild 또는 insertBefore 메서드를 사용

->현재 위치에서 노드를 제거하고 새로운 위치에 노드를 추가 ->즉, 노드가 이동

<body>
    <ul id="fruits">
    	<li>Apple</li>
        <li>Banana</li>
        <li>Orange</li>
    </ul>
</body>
<script>
    const $fruits = document.getElementById('fruits');
    
    //이미 존재하는 요소 노드를 취득
    const [$apple, $banana, ] = $fruits.children;
    //이미 존재하는 $apple 요소 노드를 #fruits 요소 노드의 마지막 노드로 이동
    #fruits.appendChild($apple); // Banana - Orange - Apple
    //이미 존재하는 $banana 요소 노드를 #fruits 요소 노드의 마지막 노드로 이동
    $fruits.insertBefore($banana, $fruits.lastElementChild);
    // Orange - Banana - Apple
</script>

 

노드 복사

✔️cloneNode([deep: true | false])

->노드의 사본을 생성

->true일 때, 깊은 복사로 자손 노드가 포함된 사본을 생성

->false일 때, 얕은 복사로 자손 노드를 복사하지 않음(텍스트 노드도 없음)

 

노드 교체

✔️replaceChild(newChild, oldChild)

->자신을 호출한 노드의 자식 노드를 다른 노드로 교체

->oldChild 노드는 DOM에서 제거

<body>
    <ul id="fruits">
    	<li>Apple</li>
    </ul>
</body>
<script>
    const $fruits = document.getElementById('fruits');
    
    //기존 노드와 교체할 요소 노드를 생성
    const $newChild = document.createElement('li');
    $newChild.textContent = 'Banana';
    
    // $fruits 요소 노드의 첫 번째 자식 요소 노드를 $newChild 요소 노드로 교체
    $fruits.replaceChild($newChild, $fruits.firstElementChild);
    // Banana
</script>

 

노드 삭제

✔️removeChild(child)

->child 매개변수에 인수로 전달한 노드를 DOM에서 삭제

<body>
    <ul id="fruits">
    	<li>Apple</li>
        <li>Banana</li>
    </ul>
</body>
<script>
    const $fruits = document.getElementById('fruits');
    
    //#fruits 요소 노드의 마지막 요소를 DOM에서 삭제
    $fruits.removeChild($fruits.lastElementChild);
    // Apple
</script>

 

HTML 어트리뷰트 조작

✔️attribute 프로퍼티는 getter만 존재하는 읽기 전용 접근자 프로퍼티

->어트리뷰트 값을 취득할 수 있지만 변경불가

✔️getAttribute / setAttribute 

->attribute 프로퍼티를 통하지 않고 직접 값을 취득하거나 변경할 수 있음

->getAttribute : 값을 참조

->setAttribute : 값을 변경

<body>
    <input id="user" type="text" value="ungmo2">
    <script>
        const $input = document.getElementById('user');
        
        //value 어트리뷰트 값을 취득
        const inputValue = $input.getAttribute('value');
        console.log(inputValue); //ungmo2
        
        //value 어트리뷰트 값을 변경
        $input.setAttribute('value', 'foo');
        console.log($input.getAttribute('value')); //foo
    </script>
</body>

✔️hasAttribute(attributeName)

->어트리뷰트 존재여부 확인

✔️removeAttribute(attributeName)

->어트리뷰트 삭제

<body>
    <input id="user" type="text" value="ungmo2">
    <script>
        const $input = document.getElementById('user');
        
        //value 어트리뷰트 존재 확인
        if($input.hasAttribute('value')){
            //value 어트리뷰트 삭제
            $input.removeAttribute('value');
        }
        
        console.log($input.hasAttribute('value')); //false
    </script>
</body>

 

HTML 어트리뷰트 VS DOM 프로퍼티

✔️요소 노드 객체에는 HTML 어트리뷰트HTML 어트리뷰트에 대응하는 프로퍼티(DOM 프로퍼티)가 존재

✔️DOM 프로퍼티들은 HTML 어트리뷰트 값을 초기값으로 가지고 있음

✔️요소 노드는 2개의 상태를 관리 ->초기 상태, 최신 상태

✔️HTML 어트리뷰트

->HTML 요소의 초기 상태를 지정

✔️DOM 프로퍼티

->최신 상태를 유지

->DOM 프로퍼티에 값을 할당 -> 최신 상태 값을 변경하는 의미

 

HTML 어트리뷰트와 DOM 프로퍼티의 대응 관계

  • id 어트리뷰트와 id 프로퍼티는 1:1 대응, 동일한 값으로 연동
  • input 요소의 value 어트리뷰트는 value 프로퍼티와 1:1 대응, 하지만 value 어트리뷰트는 초기 상태value 프로퍼티는 최신 상태를 가짐
  • class 어트리뷰트는 className, classList 프로퍼티와 대응
  • for 어트리뷰트는 htmlFor 프로퍼티와 1:1 대응
  • td 요소의 colspan 어트리뷰트는 대응하는 프로퍼티가 존재X
  • textContent 프로퍼티는 대응하는 어트리뷰트가 존재X
  • 어트리뷰트 이름은 대소문자를 구별하지 않지만 대응하는 프로퍼티 키는 카멜 케이스를 따름(maxlength -> maxLength)

data 어트리뷰트와 dataset 프로퍼티

->HTML  요소에 정의한 정의 어트리뷰트와 자바스크립트 간에 데이터를 교환할 수 있음

->data 어트리뷰트는 data-uer-id, data-role과 같이 data- 접두사 다음에 임의의 이름을 붙여 사용

✔️dataset 프로퍼티

->data 어트리뷰트 값 취득

✔️data 어트리뷰트

->data- 접두사 다음에 존재하지 않는 이름을 키로 사용하여 dataset 프로퍼티에 값을 할당하면 data 어트리뷰트가 추가

<!DOCTYPE html>
<html>
<body>
    <ul class="users">
        <li id="1" data-user-id="7621" data-role="admin">Lee</li>
        <li id="1" data-user-id="9524" data-role="subscriber">Kim</li>
    </ul>
    <script>
        const users = [...document.querySelector('.users').children];

        //user-id가 '7621'인 요소 노드를 취득
        const user = users.find(user => user.dataset.userId === '7621');
        //user-id가 '7621'인 요소 노드에서 data-role의 값을 취득
        console.log(user.dataset.role); //admin

        //user-id가 '7621'인 요소 노드의 data-role의 값을 변경
        user.dataset.role = 'subscriber';
        //dataset프로퍼티는 DOMStringMap 객체를 반환
        console.log(user.dataset); //DOMStringMap {userId : '7621', role: 'subscriber'}
    </script>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
    <ul class="users">
        <li id="1" data-user-id="7621">Lee</li>
        <li id="1" data-user-id="9524">Kim</li>
    </ul>
    <script>
        const users = [...document.querySelector('.users').children];

        //user-id가 '7621'인 요소 노드를 취득
        const user = users.find(user => user.dataset.userId === '7621');
        //user-id가 '7621'인 요소 노드에 새로운 data 어트리뷰트를 추가
        user.dataset.role = 'admin';
        console.log(user.dataset);
        /*
        DOMStringMap {userId : '7621', role: 'admin'}
        -> <li id="1" data-user-id="7621" data-role="admin">Lee</li>
        */
    </script>
</body>
</html>

 

인라인 스타일 조작

✔️style 프로퍼티

->인라인 스타일을 취득하거나 추가 또는 변경

<!DOCTYPE html>
<html>
<body>
    <div style="color: red;">Hello World</div>
    <script>
        const $div = document.querySelector('div');

        //인라인 스타일 취득
        console.log($div.style);  //CSSStyleDeclaration { 0: "color", ...}

        //인라인 스타일 변경
        $div.style.color = 'blue';

        //인라인 스타일 추가
        $div.style.width = '100px';
        $div.style.height = '100px';
        $div.style.backgroundColor = 'yellow';
    </script>
</body>
</html>

클래스 조작

✔️className

->어트리뷰트 값을 취득하거나 변경

->어트리뷰트 값을 문자열로 반환

<!DOCTYPE html>
<html>
<head>
    <style>
        .box{
            width: 100px; height: 100px;
            background-color: antiquewhite;
        }
        .red{ color: red;}
        .blue{ color: blue;}
    </style>
</head>
<body>
    <div class="box red">Hello World</div>
    <script>
        const $box = document.querySelector('box');

        //.box 요소의 class 어트리뷰트 값을 취득
        console.log($box.className);  // 'box red'

        //.box 요소의 class 어트리뷰트 값 중에서 'red'만 'blue'로 변경
        $box.className = $box.className.replace('red', 'blue');
    </script>
</body>
</html>

✔️classList

->어트리뷰트의 정보를 담은 DOMTokenList 객체를 반환

<!DOCTYPE html>
<html>
<head>
    <style>
        .box{
            width: 100px; height: 100px;
            background-color: antiquewhite;
        }
        .red{ color: red;}
        .blue{ color: blue;}
    </style>
</head>
<body>
    <div class="box red">Hello World</div>
    <script>
        const $box = document.querySelector('box');

        //.box 요소의 class 어트리뷰트 정보를 담은 DOMTokenList 객체를 취득
        // classList가 반환하는 DOMTokenList 객체는 HTMLCollection과 NodeList와 같이
        // 노드 객체의 상태 변화를 실시간으로 반영하는 살아 있는(live) 객체다.
        console.log($box.classList);  
        // DOMTokenList(2) [length: 2, value: 'box blue', 0: 'box', 1: 'blue']

        //.box 요소의 class 어트리뷰트 값 중에서 'red'만 'blue'로 변경
        $box.classList = $box.className.replace('red', 'blue');
    </script>
</body>
</html>

✔️add(...className)

->인수로 전달한 1개 이상의 문자열을 class 어트리뷰트 값으로 추가

$box.classList.add('foo'); //-> class="box red foo"
$box.classList.add('bar', 'baz'); //-> class="box red foo bar baz"

✔️remove(...className)

->인수로 전달한 1개 이상의 문자열과 일치하는 클래스를 class 어트리뷰트에서 삭제

->없을 시 에러 없이 무시

$box.classList.remove('foo'); //-> class="box red bar baz"
$box.classList.remove('bar', 'baz'); //-> class="box red"
$box.classList.remove('x'); //-> class="box red"

✔️item(index)

->index에 해당하는 클래스를 class 어트리뷰트에서 반환

-> ex. index가 0이면 첫 번째 클래스를 반환

$box.classList.item(0); //'box'
$box.classList.item(1); //'red'

✔️contains(className)

->포함되어 있는지 확인

$box.classList.contains('box'); //true
$box.classList.contains('blue'); //false

✔️replace(oldClassName, newClassName)

->첫 번째 인수로 전달한 문자열을 두 번째 인수로 전달한 문자열로 변경

$box.classList.replace('red', 'blue'); //class='box blue'

✔️toggle(className[.force])

->일치하는 클래스가 존재하면 제거, 존재하지 않으면 추가

$box.classList.toggle('foo'); //class="box blue foo"
$box.classList.toggle('foo'); //class="box blue"

->두 번째 인수로 불리언 값으로 평가되는 조건식을 전달할 수 있다.

->true면 강제로 추가

->false면 강제로 제거

✔️이 외에도 forEach, entries, keys, values, supports메서드를 제공

 

요소에 적용되어 있는 CSS 스타일 참조

->style 프로퍼티는 인라인 스타일만 반환

✔️getComputedStyle

->HTML 요소에 적용되어 있는 모든 CSS 스타일을 참조

<!DOCTYPE html>
<html>
<head>
    <style>
        body{ color: red;}
        .box{
            width: 100px; height: 50px;
            background-color: cornsilk;
            border: 1px solid black;
        }
    </style>
</head>
<body>
    <div class="box">Box</div>
    <script>
        const $box = document.querySelector('box');

        //.box 요소의 적용된 모든 CSS 스타일을 담고 있는 CSSStyleDeclaration 객체를 취득
        const computedStyle = window.getComputedStyle($box);
        console.log(computedStyle);  //CSSStyleDeclaration

        //임베딩 스타일
        console.log(computedStyle.width); //100px
        console.log(computedStyle.height); //50px
        console.log(computedStyle.backgroundColor); //rgb(255, 248, 220)
        console.log(computedStyle.border); //1px solid rgb(0, 0, 0)

        //상속 스타일
        console.log(computedStyle.color); //rgb(255, 0, 0)

        //기본 스타일
        console.log(computedStyle.display); //block
    </script>
</body>
</html>

->두 번째 인자로 :after, :before 같은 의사 요소를 지정하는 문자열 전달 가능

->의사 요소 아닌 것은 두 번째 인수 생략

<!DOCTYPE html>
<html>
<head>
    <style>
        .box::before{
            content: 'Hello';
        }
    </style>
</head>
<body>
    <div class="box">Box</div>
    <script>
        const $box = document.querySelector('box');

        //의사 요소 :before의 스타일을 취득
        const computedStyle = window.getComputedStyle($box, ':before')
        console.log(computedStyle.content); //'Hello'
    </script>
</body>
</html>