Developer's Development

3.4.5 [화면 구현] JavaScript: DOM 본문

AI 활용 애플리케이션 개발/화면 구현

3.4.5 [화면 구현] JavaScript: DOM

mylee 2025. 10. 14. 14:26
DOM (Document Object Model)

 

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

 

👉🏻 노드 (node)

HTML 요소는 렌더링 엔진에 의해 파싱되어 DOM을 구성하는 요소 노드 객체로 변환된다. 이떄 HTML 요소의 어트리뷰트는 어트리뷰트 노드로, HTML 요소의 텍스트 콘텐츠는 텍스트 노드로 변환된다.

 

  • get element node (노드 취득)

👉🏻 use id (id 사용)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>01_use id</title>
    <style>
        .area {
            width: 100px;
            height: 100px;
            border: 1px solid lightgray;
        }
    </style>
</head>
<body>
    <h1>01. id를 이용한 요소 노드 취독</h1>
    <div id="area1" class="area">
        div 영역
        <p id="area2">p 영역</p>
    </div>
    <div id="area1" class="area">
        div 영역
    </div>

    <script>
        // id 값이 일치하는 '하나의' 요소 노드만 취득
        const $elem = document.getElementById('area1');
        $elem.style.backgroundColor = 'yellow';

        // id명을 변수처럼 사용 가능하나, JS 전역 변수와 겹칠 수 있으므로 사용 지양
        // let area2 = 100;
        area2.style.backgroundColor = 'red';

        // 존재하지 않는 id를 탐색하면 null 반환
        const $noElem = document.getElementById('area3');
        console.log($noElem);
        $noElem.style.backgroundColor = 'blue';
    </script>
</body>
</html>

 

👉🏻 use tag name (태그명 사용)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>02_use tag name</title>
</head>
<body>
    <h1>02. 태그 이름을 이용한 요소 노드 취득</h1>
    <ul id="food">
        <li>돼지고기김치찌개</li>
        <li>두부된장찌개</li>
        <li>고등어구이정식</li>
    </ul>
    <ul id="drink">
        <li>아메리카노</li>
        <li>카페라떼</li>
        <li>토마토주스</li>
    </ul>

    <script>
        const $lists = document.getElementsByTagName('li'); // HTMLCollection 객체 반환
        console.log($lists);

        let changeColor = 0;
        for(let i=0; i<$lists.length; i++) {
            $lists[i].style.backgroundColor = 'rgb(130, 220,' + changeColor + ')';
            changeColor += 50;
        }
        Array.from($lists).forEach(list => list.style.color = 'white');

        const $drink = document.getElementById('drink');
        const $listsFromDrink = $drink.getElementsByTagName('li');
        [...$listsFromDrink].forEach(list => list.style.color = 'red');

        // 존재하지 않는 태그명으로 탐색하면 빈 HTMLCollection 객체 반환
        const $noElem = document.getElementsByTagName('div');
        console.log($noElem);
    </script>
</body>
</html>

 

👉🏻 use class name (클래스명 사용)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>03_use class</title>
</head>
<body>
    <h1>03. class를 이용한 요소 노드 취득</h1>
    <ul id="available">
        <li class="drink coffee">커피</li>
        <li class="drink milk">우유</li>
        <li class="drink coke">콜라</li>
    </ul>
    <ul id="unavailable">
        <li class="drink soju">소주</li>
        <li class="drink beer">맥주</li>
        <li class="drink liquor">양주</li>
    </ul>

    <script>
        // const $drinks = document.getElementsByClassName('drink');
        const $drinks = document.getElementsByClassName('drink coke');
        console.log($drinks);

        Array.from($drinks).forEach(drink => drink.style.color = 'skyblue');
    </script>
</body>
</html>

 

👉🏻 use css (선택자 사용)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>04_use css</title>
</head>
<body>
    <h1>04. CSS 선택자를 이용한 요소 노드 취득</h1>

    <div class="area">
        <p>first</p>
    </div>
    <div class="area">
        <p>second</p>
    </div>

    <script>
        // 해당 CSS 선택자에 해당되는 요소 중 처음 하나만 반환
        const $area = document.querySelector('.area');
        console.log($area);

        $area.style.backgroundColor = 'yellow';

        const $first = $area.querySelector('p');
        console.log($first);

        // 존재하지 않는 요소 탐색 시 null 반환
        const $noElem = document.querySelector('#noElem');
        console.log($noElem);
    </script>

    <ul id="list">
        <li class="drink">아메리카노</li>
        <li class="drink">카페라떼</li>
        <li class="food">김밥</li>
        <li class="food">라면</li>
    </ul>

    <script>
        // 해당 CSS 선택자에 해당하는 모든 요소를 NodeList로 반환
        const $lists = document.querySelectorAll('ul > li');
        console.log($lists);

        $lists.forEach(list => list.style.backgroundColor = 'lightpink');

        const $drinks = document.getElementById('list').querySelectorAll('.drink');
        $drinks.forEach(drink => drink.style.color = 'white');

        // 존재하지 않는 요소 탐색 시 빈 NodeList 반환
        const $noElemList = document.querySelectorAll('#noElemList');
        console.log($noElemList);
    </script>
</body>
</html>

 

👉🏻 HTMLCollection과 NodeList

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>05_HTMLCollection and NodeList</title>
    <style>
        .white {color: white;}
        .black {color: black;}
        .blue {color: blue;}
        .red {color: red;}
    </style>
</head>
<body>
    <h1>05. HTMLCollection과 NodeList</h1>

    <h3>HTMLCollection</h3>
    <pre>
    - getElementsByTagName, getElementsByClassName 메서드가 반환하는 객체
    - 노드 객체의 상태 변화를 실시간으로 반영하는 live DOM 컬렉션 객체
    </pre>

    <ul>
        <li class="white">착한 사람 눈에만 보이는 첫 번째 글</li>
        <li class="white">착한 사람 눈에만 보이는 두 번째 글</li>
        <li class="white">착한 사람 눈에만 보이는 세 번째 글</li>
    </ul>

    <script>
        const $whiteList = document.getElementsByClassName('white');
        console.log($whiteList);

        // 문제. HTMLCollection이 live한 객체라서 정상 적용되지 않음
        // for(let i=0; i<$whiteList.length; i++) {
        //     $whiteList[i].className = 'black';
        // }

        // 해결 1. 반복문 역방향 순회
        for(let i=$whiteList.length-1; i>=0; i--) {
            $whiteList[i].className = 'black';
        }

        // 해결 2. while문 사용
        /* while(조건식) {
            수행할 내용
        } */
        let i = 0;
        while($whiteList.length > 0) {
            $whiteList[i].className = 'black';
        }

        // 해결 3. [권장] 배열로 변환하여 사용
        Array.from($whiteList).forEach(list => list.className = 'black');
    </script>

    <h3>NodeList</h3>
    <pre>
    - querySelectorAll 메서드가 반환하는 객체
    - 실시간으로 노드 객체 상태를 변경하지 않으므로(non-live) HTMLCollection 부작용 헤결
    </pre>

    <ul id="lists">
        <li class="red">빨간 휴지 줄까~ 파란 휴지 줄까~</li>
        <li class="red">빨간 휴지 줄까~ 파란 휴지 줄까~</li>
        <li class="red">빨간 휴지 줄까~ 파란 휴지 줄까~</li>
    </ul>

    <script>
        const $redList = document.querySelectorAll('.red');

        for(let i=0; i<$redList.length; i++) {
            $redList[i].className = 'blue';
        }

        // [주의] chileNodes를 통해 반환되는 NodeList는 live하게 동작함
        const $lists = document.getElementById('lists');
        const childNodes = $lists.childNodes;
        console.log(childNodes);

        for(let i=0; i<childNodes.length; i++) {
            $lists.removeChild(childNodes[i]);
        }
        console.log(childNodes);
    </script>
</body>
</html>

 

 

  • 노드 탐색

👉🏻 child node (자식 노드)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=<h1>, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>01. 자식 노드 탐색</h1>

    <ol id="node">
        <!-- Node.prototype -->
        <li>childNodes: 자식노드(요소 노드, 텍스트 노드)를 탐색하여 NodeList로 반환</li>
        <li>firstChild: 첫 번째 자식 노드(요소 노드, 텍스트 노드)를 반환</li>
        <li>lastChild: 마지막 자식 노드(요소 노드, 텍스트 노드)를 반환</li>
    </ol>

    <script>
        const $node = document.getElementById('node');
        console.log($node.childNodes);
        console.log($node.firstChild);
        console.log($node.lastChild);
    </script>

    <ol id="element">
        <!-- Element.prototype -->
        <li>children: 자식 노드 중 요소 노드만 탐색해 HTMLCollection 객체로 반환</li>
        <li>firstElementChild: 첫 번째 자식 요소 노드 반환</li>
        <li>lastElementChild: 마지막 자식 요소 노드 반환</li>
    </ol>

    <script>
        const $element = document.getElementById('element');
        console.log($element.children);
        console.log($element.firstElementChild);
        console.log($element.lastElementChild);
    </script>

    <ol id="empty"></ol>

    <script>
        const $empty = document.getElementById('empty');

        // 자식 노드 존재 여부 확인 (요소 노드, 텍스트 노드)
        console.log($node.hasChildNodes());
        console.log($element.hasChildNodes());
        console.log($empty.hasChildNodes());

        // 요소 노드만 존재하는지 확인하고 싶다면?
        console.log(!!$empty.children.length);
        console.log(!!$empty.childrenElmentCount);
    </script>
</body>
</html>

 

👉🏻 parent node (부모 노드)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>02. 부모 노드 탐색</h1>

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

 

👉🏻 sibling node (형제 노드)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>03. 형제 노드 탐색</h1>

    <ol id="node">
        <!-- Node.prototype -->
        <li>previousSibling: 형제 노드 중 자신의 이전 형제 노드(요소 노드, 텍스트 노드)를 탐색하여 반환</li>
        <li>nextSibling: 형제 노드 중 자신의 다음 형제 노드(요소 노드, 텍스트 노드)를 탐색하여 반환</li>
    </ol>

    <script>
        const $node = document.getElementById('node');

        const firstChild = $node.firstChild;
        const nextSibling = firstChild.nextSibling;
        const prevSibling = nextSibling.previousSibling;
        console.log(firstChild, nextSibling, prevSibling);
    </script>

    <ol id="element">
        <!-- Element.prototype -->
        <li>previousElementSibling: 형제 요소 노드 중 자신의 이전 형제 요소 노드 탐색하여 반환</li>
        <li>nextElementSibling: 형제 요소 노드 중 자신의 다음 형제 요소 노드 탐색하여 변환</li>
    </ol>

    <script>
        const $element = document.getElementById('element');

        const firstElementChild = $element.firstElementChild;
        const nextElemSibling = firstElementChild.nextElementSibling;
        const prevElemSibling = nextElemSibling.previousSibling;
        console.log(firstElementChild, nextElemSibling, prevElemSibling);
    </script>
</body>
</html>

 

 

  • 노드 프로퍼티

👉🏻 get node info (노드 정보 취득)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>01. 노드 정보 취득</h1>

    <pre>
    Node.prototype.nodeType : 노드 객체의 종류, 즉 노드 타입을 나타내는 상수 반환
        Node.ELEMENT_NODE : 요소 노드 타입을 나타내는 상수 1 반환
        Node.TEXT_NODE : 텍스트 노드 타입을 나타내는 상수 3 반환
        Node.DOCUMENT_NODE : 문서 노드 타입을 나타내는 상수 9를 반환
    Node.prototype.nodeName : 노드의 이름을 문자열로 반환
        요소 노드 : 다문자 문자열로 태그 이름을 반환
        텍스트 노드 : 문자열 "#text"를 반환
        문서 노드 : 문자열 "#document"를 반환
    </pre>

    <div id="area">노드 정보 취득!</div>
    <script>
        console.log(document.nodeType);
        console.log(document.nodeName);

        const $area = document.getElementById('area');
        console.log($area.nodeType);
        console.log($area.nodeName);

        const $textNode = $area.firstChild;
        console.log($textNode.nodeType);
        console.log($textNode.nodeName);
    </script>
</body>
</html>

 

👉🏻 text node content (요소 노드의 텍스트 조작)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>02. 요소 노드의 텍스트 조작</h1>

    <h3>nodeValue</h3>
    
    <div id="area">nodeValue</div>
    <script>
        console.log(document.nodeValue);

        const $area = document.getElementById('area');
        const $textNode = $area.firstChild;
        console.log($textNode);
        console.log($area.nodeValue);
        console.log($textNode.nodeValue);

        $textNode.nodeValue = '텍스트 값 변경!!!';
    </script>

    <h3>textContent</h3>

    <div id="area2">textContent<span>내부 span</span></div>
    <script>
        const $area2 = document.getElementById('area2');

        console.log($area2.textContent);

        console.log($area2.nodeValue);
        console.log($area2.firstChild.nodeValue);
        console.log($area2.lastChild.firstChild.nodeValue);

        $area2.textContent = '텍스트 값 변경! <span>span 태그도 써볼게요~</span>';
    </script>
</body>
</html>

 

 

  • 노드 수정

👉🏻 innerHTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>01. innerHTML</h1>

    <div id="area">태그 엘리먼트의 값을 읽거나, 변경할 때 <span>innerHTML</span>속성을 사용한다.</div>
    <script>
        const $area = document.getElementById('area');
        console.log($area.innerHTML);

        $area.innerHTML += '값을 붙여볼게요 짠!';
        $area.innerHTML = '';
        $area.innerHTML = '<h1>innerHTML</h1> 속성으로 값 변경?!';
    </script>
    
    <ul id="list">
        <li class="coffee">카페라떼</li>
    </ul>
    <script>
        const $list = document.getElementById('list');
        $list.innerHTML += '<li class="coke">콜라</li>';
    </script>

    <script>
        $area.innerHTML = `<img src='x' onerror='alert("메롱! 악성 코드지롱!")'/>`;
    </script>
</body>
</html>

 

👉🏻 insertAdjacentHTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>02. insertAdjacentHTML</h1>

    <div id="area">
        insertAdjacentHTML method 사용 테스트
    </div>
    <script>
        const $area = document.getElementById('area');

        $area.insertAdjacentHTML('beforebegin', '<h1>BB</h1>');
        $area.insertAdjacentHTML('afterbegin', '<h1>AB</h1>');
        $area.insertAdjacentHTML('beforeend', '<h1>BE</h1>');
        $area.insertAdjacentHTML('afterend', '<h1>AE</h1>');
    </script>
</body>
</html>

 

👉🏻 node create append

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>03. 노드 생성과 추가</h1>

    <ul id="drink">
        <li>카페라떼</li>
    </ul>
    <script>
        const $drink = document.getElementById('drink');

        const $li = document.createElement('li');   // <li></li>
        const textNode = document.createTextNode('우유');
        $li.appendChild(textNode);
        // $li.textContent = '우유';                   // <li>우유</li>
        $drink.appendChild($li);
    </script>

    <h3>복수의 노드 생성 및 추가</h3>

    <ul id="food">
        <li>불고기 백반</li>
    </ul>
    <script>
        const $food = document.getElementById('food');

        // const $container = document.createElement('div');

        const $fragment = document.createDocumentFragment();

        ['햄버거 세트', '샌드위치 콤보', '짬짜면'].forEach(
            text => {
                const $li = document.createElement('li');
                $li.textContent = text;

                // (A) DOM이 3번 변경 > 리플로우/리페인트 3번 실행 == 비효율적
                // $food.appendChild($li);

                // $container.append($li);

                $fragment.append($li);
            }
        );

        // (B) 컨테이너 요소 div를 사용하여 DOM 1번만 변경 ((A)의 문제 해결)
        //     그러나 불필요한 요소 div가 DOM에 추가됨
        // $food.appendChild($container);
        
        $food.appendChild($fragment);
    </script>
</body>
</html>

 

👉🏻 node insert & move

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>04. 노드 삽입 및 이동</h1>

    <h3>노드 삽입</h3>

    <ul id="drink">
        <li>카페라떼</li>
    </ul>
    <script>
        const $drink = document.getElementById('drink');

        const $li1 = document.createElement('li');
        $li1.textContent = '우유';
        $drink.appendChild($li1);

        const $li2 = document.createElement('li');
        $li2.textContent = '콜라';
        // $drink.insertBefore($li2, $drink.lastElementChild);
        // $drink.insertBefore($li2, document.querySelector('h3'));
        $drink.insertBefore($li2, null);
    </script>

    <h3>노드 이동</h3>
    
    <ul id="food">
        <li>불고기 백반</li>
        <li>햄버거 세트</li>
    </ul>
    <script>
        const $food = document.getElementById('food');

        const [$bulgogi, $hamburger] = $food.children;

        $drink.appendChild($bulgogi);
        $drink.insertBefore($hamburger, $bulgogi);
    </script>
</body>
</html>

 

👉🏻 node copy & replace & remove

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>05. 노드 복사/교체/삭제</h1>

    <h3>노드 복사</h3>

    <ul class="copy">
        ul area
        <li>li area</li>
    </ul>
    <script>
        const $ul = document.querySelector('.copy');
        const $li = $ul.firstElementChild;

        const $clone1 = $li.cloneNode(false);   // 얕은 복사
        const $clone2 = $li.cloneNode(true);    // 깊은 복사

        $ul.appendChild($clone1);
        $ul.appendChild($clone2);
    </script>

    <h3>노드 교체</h3>

    <ul id="drink">
        <li>카페라떼</li>
        <li>아메리카노</li>
    </ul>
    <script>
        const $drink = document.getElementById('drink');
        const $latte = $drink.firstElementChild;

        const $newLatte = document.createElement('li');
        $newLatte.textContent = '바닐라빈 라떼';

        $drink.replaceChild($newLatte, $latte);
    </script>

    <h3>노드 삭제</h3>
    
    <ul id="food">
        <li>불고기 백반</li>
        <li>햄버거 세트</li>
    </ul>
    <script>
        const $food = document.getElementById('food');
        $food.removeChild($food.lastElementChild);
    </script>
</body>
</html>

 

 

  • attribute

👉🏻 attribute (어트리뷰트)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>01_attribute</title>
</head>
<body>
    <h1>01. 어트리뷰트</h1>

    <label for="username">유저명</label>
    <input type="text" id="username" value="user01">

    <script>
        const attributes = document.getElementById('username').attributes;

        console.log(attributes);
        console.log(attributes.type.value);
        console.log(attributes.id.value);
        console.log(attributes.value.value);
    </script>

    <pre>
    Element.prototype.getAttribute/setAttribute 메서드를 사용하면 attributes 프로퍼티를 통하지 않고 
    요소 노드에서 메서드를 통해 직접 HTML 어트리뷰트 값을 취득하거나 변경할 수 있어 편리하다. 
    </pre>

    <script>
        const $input = document.getElementById('username');

        const inputValue = $input.getAttribute('value');
        console.log('getAttribute', inputValue);

        $input.setAttribute('value', 'user99');
    </script>

    <pre>
    hasAttribute(attributeName) 메서드를 사용하면 특정 HTML 어트리뷰트가 존재하는지 확인할 수 있다.
    removeAttribute(attributeName) 메서드를 사용하면 특정 HTML 어트리뷰트를 삭제할 수 있다. 
    </pre>

    <label for="nickname">닉네임</label>
    <input type="text" id="nickname" value="JSMaster">

    <script>
        const $nickname = document.getElementById('nickname');

        console.log($nickname.hasAttribute('name'));

        if($nickname.hasAttribute('value')) {
            $nickname.removeAttribute('value');
        }
        console.log($nickname.hasAttribute('value'));
    </script>
</body>
</html>

 

👉🏻 attribute and property

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>02_attribute and property</title>
</head>
<body>
    <h1>02. HTML 어트리뷰트와 DOM 프로퍼티</h1>
    
    <pre>
    HTML 어트리뷰트 : HTML 요소의 초기 상태를 지정하며 변하지 않는다.
                     어트리뷰트 노드에서 관리되며 값을 얻거나 변경하려면 getAttribute/setAttribute 메서드를 사용한다. 

    DOM 프로퍼티 : 사용자가 입력한 최신 상태를 관리한다. 
                  사용자의 입력에 의한 상태 변화에 반응하여 언제나 최신 상태를 유지한다.
    </pre>
    
    <label for="username">유저명</label>
    <input type="text" id="username" value="user01">
    <label for="nickname">닉네임</label>
    <input type="text" id="nickname" value="JSBeginner">

    <script>
        const $user = document.getElementById('username');

        $user.oninput = () => {
            console.log('value property:', $user.value);
            console.log('value attribute:', $user.getAttribute('value'));
        };

        const $nickname = document.getElementById('nickname');
        $nickname.value = 'JSMaster';

        console.log('value property:', $nickname.value);
        console.log('value attribute:', $nickname.getAttribute('value'));

        // id는 사용자 입력에 의한 상태 변화와 관계 없는 attribute이므로
        // id attribute와 id property는 항상 동일한 값으로 연동됨
        $nickname.id = 'nick';
        console.log($nickname.id);
        console.log($nickname.getAttribute('id'));
    </script>

    <pre>
    getAttribute 메서드로 취득한 어트리뷰트 값은 언제나 문자열이다.
    하지만 DOM 프로퍼티로 취득한 최신 상태 값은 문자열이 아닐 수 있다.
    checkbox 요소의 checked 어트리뷰트 값은 문자열이지만 checked 프로퍼티 값은 불리언 타입이다.
    </pre>
    
    <label for="check">확인</label>
    <input type="checkbox" id="check" checked="true">

    <script>
        const $checkbox = document.querySelector('input[type=checkbox]');

        console.log($checkbox.getAttribute('checked'));
        console.log($checkbox.checked);
    </script>
</body>
</html>

 

👉🏻 data attribute & dataset attribute

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>03_data attribute dataset property</title>
</head>
<body>
    <h1>03. data 어트리뷰트와 dataset 프로퍼티</h1>

    <pre>
    data 어트리뷰트와 dataset 프로퍼티로 HTML 요소에 정의한 사용자 정의 어트리뷰트와 자바스크립트 간 데이터 교환이 가능하다. 
    data 어트리뷰트는 data- 접두사 다음에 임의의 이름을 붙여 사용한다.
    HTMLElement.dataset 프로퍼티로 DOMStringMap 객체 반환받으면 임의의 이름을 카멜 케이스로 변환한 프로퍼티를 가지고 있다. 
    </pre>

    <ul class="boardlist">
        <li data-board-id="13" data-category-id="1">망원 맛집 추천 부탁드려요!</li>
        <li data-board-id="15" data-category-id="2">한강 러닝 크루 구해요!</li>
        <li data-board-id="18" data-category-id="3">요즘 재테크 어떻게 하시나요?</li>
    </ul>

    <script>
        const boardlist = Array.from(document.querySelector('.boardlist').children);

        boardlist.forEach(board => console.log(board.dataset));

        const board = boardlist.find(board => board.dataset.categoryId === '1');
        board.dataset.categoryId = '4';

        board.dataset.boardWriter = 'JSMaster';
    </script>
</body>
</html>

 

 

  • 스타일

👉🏻 inline style

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>01_inline style</title>
</head>
<body>
    <h1>01. inline style</h1>
    <pre>
    HTMLElement.prototype.style 프로퍼티는 setter와 getter가 모두 존재하는 접근자 프로퍼티로
    요소 노드의 인라인 스타일을 취득하거나 추가 또는 변경한다. 
    HTMLElement.prototype.style 프로퍼티는 CSSStyleDeclaration 타입의 객체 반환하는데
    이 프로퍼티에 값을 할당하면 해당 CSS 프로퍼티가 인라인 스타일로 HTML 요소에 추가되거나 변경된다.
    </pre>
    <div style="color: white;">AREA</div>

    <script>
        const $area = document.querySelector('div');

        console.log($area.style);

        $area.style.width = '100px';
        $area.style.height = '100px';

        // CSSStyleDeclaration 객체의 프로퍼티 == 카멜 케이스
        $area.style.backgroundColor = 'gray';

        // CSS 프로퍼티 == 케밥 케이스
        $area.style['background-color'] = 'red';
    </script>
</body>
</html>

 

👉🏻 className classList

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>02_className classList</title>
    <style>
        .area { 
            width : 100px; 
            height : 100px; 
            border : 1px solid black;
        }

        .circle { border-radius: 50%; }

        .lightgray { background : lightgray; }
        .yellow { background : yellow; }
    </style>
</head>
<body>
    <h1>02. className과 classList</h1>

    <pre>
    class 어트리뷰트에 대응하는 DOM 프로퍼티는 class가 아니라 className, classList이다. 
    Element.prototype.className 프로퍼티는 setter와 getter 모두 존재하는 접근자 프로퍼티로 
    요소 노드의 className 프로퍼티를 참조하면 class 어트리뷰트의 값을 문자열로 반환하고, 
    요소 노드의 className 프로퍼티에 문자열을 할당하면 class 어트리뷰트 값을 할당한 문자열로 변경한다.
    Element.prototype.classList 프로퍼티는 class 어트리뷰트의 정보를 담은 DOMTokenList 객체를 반환한다. 
    </pre>
    
    <div class='area'></div>

    <script>
        const $area = document.querySelector('.area');

        // className 프로퍼티에 값을 할당하면 클래스명 덮어씀
        // $area.className = 'circle';

        console.log($area.className);
        console.log($area.classList);

        // add(...className), remove(...className)
        $area.classList.add('circle');
        $area.classList.add('lightgray');
        // $area.classList.add('circle', 'lightgray');
        // $area.classList.remove('lightgray');

        // item(index)
        console.log($area.classList.item(0))
        console.log($area.classList.item(2))

        // contains(className)
        console.log($area.classList.contains('lightgray'));
        console.log($area.classList.contains('yellow'));

        // replace(oldClassName, newClassName)
        $area.classList.replace('lightgray', 'yellow');

        // toggle(className)
        $area.classList.toggle('circle');
        $area.classList.toggle('circle');
        $area.classList.toggle('circle');
    </script>
</body>
</html>

 

 

Event

 

브라우저는 클릭, 키보드 입력, 마우스 이동 등이 일어나면 이를 감지하여 특정한 타입의 이벤트를 발생시킨다.

이벤트가 발생했을 때 호출 될 함수를 이벤트 핸들러(Event Handler)라고 하고, 이벤트가 발생했을 때 브라우저에게 이벤트 핸들러의 호출을 위임하는 것을 이벤트 핸들러 등록이라고 한다. 이벤트와 그에 대응하는 함수(이벤트 핸들러)를 통해 사용자와 애플리케이션은 상호작용 할 수 있다.

 

  • 이벤트 핸들러

👉🏻 event handler attribute

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>01. 이벤트 핸들러 어트리뷰트</h1>

    <h3>이벤트 핸들러 등록</h3>
    <pre>
    이벤트 핸들러: 이벤트가 발생했을 때 브라우저에 호출을 위임한 함수
    - 이벤트가 발생했을 때 브라우저에게 이벤트 핸들러의 호출을 위임하는 것을
        이벤트 핸들러 등록이라고 하며, 등록 방법은 3가지가 있다.
    </pre>

    <h3>(1) 이벤트 핸들러 어트리뷰트 방식</h3>
    <pre>
    - 어트리뷰트 값으로 함수 호출문을 할당하여 이벤트 핸들러 등록 (on + 이벤트 타입)
    </pre>

    <button onclick="alert('Click!!!'); console.log('clicked');">Click me!</button>
    <button onmouseover="hello('맹구');">마우스를 올려보세요!</button>

    <script>
        function hello(name) {
            alert(`${name}님 환영합니다`);
        }
    </script>
</body>
</html>

 

👉🏻 event handler property

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h3>(2) 이벤트 핸들러 프로퍼티 방식</h3>

    <button id="btn">Click me!</button>
    <script>
        const $button = document.getElementById('btn');

        $button.onclick = function() {
            alert('DOM 프로퍼티 방식으로 이벤트 핸들러 등록!');
        };
        $button.onclick = () => alert('이벤트 하나 더 등록!');  // 얘만 실행
    </script>
</body>
</html>

 

👉🏻 addEventListener

<body>
    <h3>(3) addEventListener 메서드 방식</h3>

    <button id="btn">Click me!</button>

    <script>
        const $button = document.getElementById('btn');

        $button.addEventListener('click', function() {
            alert("클ㅡㅡㅡ릭!");
        });

        // 이벤트 핸들러 프로퍼티에 바인딩된 함수 동작에 영향을 주지 않음
        $button.onclick = function () {
            alert('DOM 프로퍼티 방식으로 이벤트 핸들러 등록!');
        };

        // 동일한 HTML 요소에서 발생한 동일한 이벤트에 함수 추가 등록 가능
        $button.addEventListener('click', function() {
            alert("한번 더 클ㅡㅡㅡ릭!");
        });

        // 참조가 동일한 이벤트 핸들러를 중복 등록하면 하나만 동작
        const handleMouseOver = () => console.log('MOUSE OVER');
        $button.addEventListener('mouseover', handleMouseOver);
        $button.addEventListener('mouseover', handleMouseOver);
        $button.addEventListener('mouseover', handleMouseOver);
    </script>
</body>

 

👉🏻 event handler remove

<body>
    <h1>04. 이벤트 핸들러 제거</h1>

    <button id="btn">Click me!</button>
    <script>
        const $button = document.getElementById('btn');

        const handleClick = () => alert('클 릭 함 !!!');

        $button.addEventListener('click', handleClick);
        $button.removeEventListener('click', handleClick);
    </script>

    <script>
        $button.addEventListener('mouseover', () => alert('마우스 올 림!'));
        $button.removeEventListener('mouseover', () => alert('마우스 올 림!'));
    </script>

    <script>
        const handleDoubleClick = () => alert("더 블 클 릭 !!!");

        // 프로퍼티로 등록된 이벤트 핸들러는 removeEventListener로 제거 불가
        // => null 할당으로 제거 가능
        $button.ondblclick = handleDoubleClick;
        $button.removeEventListener('dbclick', handleDoubleClick);

        $button.ondblclick = null;
    </script>
</body>

 

 

  • 이벤트 객체

👉🏻 event object (이벤트 객체)

<body>
    <h1>01. 이벤트 객체</h1>

    <h2 class="message">아무 곳이나 클릭해 보세요!</h2>
    <script>
        const $msg = document.querySelector('.message');

        function showCoords(e) {
            console.log(e);
            $msg.textContent = `clientX: ${e.clientX}, clientY: ${e.clientY}`;
        }

        document.onclick = showCoords;
    </script>

    <div class="area" onclick="showDivCoords(event)">
        영역 내부를 클릭해 보세요!
    </div>
    <script>
        const $area = document.querySelector('.area');

        function showDivCoords(e) {
            console.log(e);
            $area.textContent = `clientX: ${e.clientX}, clientY: ${e.clientY}`;
        }
    </script>
</body>

 

👉🏻 this (이벤트 핸들러 내부의 this)

<body>
    <h1>02. 이벤트 핸들러 내부의 this</h1>

    <h3>이벤트 핸들러 어트리뷰트</h3>
    <button onclick="handleClick1()">클릭111!</button>
    <button onclick="handleClick2(this)">클릭222!</button>
    <script>
        function handleClick1() {
            console.log(this);      // 일반 함수의 this == 전역 객체 window
        }

        function handleClick2(button) {
            console.log(button);    // 이벤트 핸들러 호출 시 전달한 this == 이벤트를 바인딩한 DOM 요소
        }
    </script>

    <h3>이벤트 핸들러 프로퍼티, addEventListener</h3>
    <button id="btn1">Click!</button>
    <button id="btn2">Click!</button>
    <script>
        const $btn1 = document.getElementById('btn1');
        const $btn2 = document.getElementById('btn2');
        $btn1.onclick = function(e) {
            console.log(this);
            console.log(this === e.currentTarget);
        };
        $btn2.addEventListener('click', function(e){
            console.log(this);
            console.log(this === e.currentTarget);
        });
    </script>

    <h3>화살표 함수</h3>
    <button id="btn3">Click!</button>
    <button id="btn4">Click!</button>
    <script>
        const $btn3 = document.getElementById('btn3');
        const $btn4 = document.getElementById('btn4');
        $btn3.onclick = e => {
            console.log(this);      // 화살표 함수의 this == 전역 객체 window
            console.log(this === e.currentTarget);
        };
        $btn4.addEventListener('click', e => {
            console.log(this);      // 화살표 함수의 this == 전역 객체 window
            console.log(this === e.currentTarget);
        });
    </script>
</body>

 

 

  • 이벤트 전파

👉🏻 event propagation (이벤트 전파)

<body>
    <h1>01. 이벤트 전파</h1>
    <pre>
    - 생성된 이벤트 객체는 이벤트를 발생시킨 DOM 요소인 이벤트 타깃을 중심으로 DOM 트리를 통해 전파된다.
    1. capturing phase
    2. target phase
    3. bubbling phase
    </pre>

    <ul id="drink">
        <li>카페라떼</li>
        <li>제로콜라</li>
        <li>바나나우유</li>
    </ul>
    <script>
        const $drink = document.getElementById('drink');

        $drink.addEventListener('click', e => {
            console.log(e.eventPhase);
            console.log(e.target);
            console.log(e.currentTarget);
            console.log('==============================')
        }, true);

        $drink.addEventListener('click', e => {
            console.log(e.eventPhase);
            console.log(e.target);
            console.log(e.currentTarget);
        });
    </script>
</body>

 

👉🏻 event delegation (이벤트 위임)

<body>
    <h1>02. 이벤트 위임</h1>

    <ul id="drink">
        <li>카페라떼</li>
        <li>제로콜라</li>
        <li>바나나우유</li>
    </ul>
    <script>
        const $drink = document.getElementById('drink');

        $drink.addEventListener('click', e => {
            if(e.target.matches('li')) {
                highlight(e.target);
            }
        });

        function highlight(elem) {
            elem.classList.toggle('highlight');
        }
    </script>
</body>

 

 

  • 이벤트 기본 동작

👉🏻 prevent browser action (브라우저 기본 동작 중단)

<body>
    <h1>01. 브라우저 기본 동작 중단</h1>
    <pre>
    이벤트 객체의 preventDefault 메서드는 DOM 요소의 기본 동작을 중단시킨다. 
    예를 들어 a 요소를 클릭하면 href 어트리뷰트에 지정된 링크로 이동하고, 
    checkbox 또는 radio 요소를 클릭하면 체크 또는 해제되는 것 등을 기본 동작이라고 한다. 
    </pre>

    <a href="https://www.google.com">클릭해도 절대 이동할 수 없는 a태그</a>
    <input type="checkbox">클릭해도 절대 체크되지 않는 체크박스 

    <script>
        document.querySelector('a').addEventListener('click', e => {
            e.preventDefault();
        });

        document.querySelector('input[type=checkbox]').addEventListener('click', e => {
            e.preventDefault();
        })
    </script>
</body>

 

👉🏻 stop event propagation (이벤트 전파 방지)

<body>
    <h1>02. 이벤트 전파 방지</h1>
    <pre>
    이벤트 객체의 stopPropagation 메서드는 이벤트 전파를 중지시킨다.
    </pre>

    <ul id="drink">
        <li>커피</li>
        <li>콜라</li>
        <li>우유</li>
    </ul>

    <script>
        const $drink = document.getElementById('drink');

        $drink.addEventListener('click', e => {
            if(e.target.matches('li')) {
                e.target.style.color = 'red';
            }
        });

        document.querySelector('li').addEventListener('click', e => {
            e.stopPropagation();
            e.target.style.color = 'blue';
        });
    </script>
</body>

 

 

ASYNC

 

  • Timer (타이머)
<body>
    <h1>01. 비동기</h1>

    <h3>동기 처리 (synchronous)</h3>
    <script>
        function sleep(func, delay) {
            const delayUntil = Date.now() + delay;
            while(Date.now() < delayUntil);
            func();
        }

        function delay() {
            console.log('나는 실행을 늦추고 싶은 함수야...');
        }

        function start() {
            console.log('나는 바로 실행하고 싶은 함수인데?!');
        }

        // sleep(delay, 3000);
        // start();
    </script>

    <h3>비동기 처리 (asynchronous)</h3>
    <pre>
        setTimeout(func|code[, delay, param1, param2, ...]);
    </pre>
    <script>
        setTimeout(delay);
        // setTimeout((msg) => console.log(`${msg}`), 3000, '3초 기다림!!!');
        start();
    </script>

    <pre>
        clearTimeout(timerId);
    </pre>
    <script>
        const timerId = setTimeout(() => console.log('나는 취소되고 말거야!'), 3000);
        console.log('timerId:', timerId);
        clearTimeout(timerId);
    </script>

    <h3>태스크 큐 (task queue)</h3>
    <pre>
        비동기 함수의 콜백 함수 또는 이벤트 핸들러가 일시적으로 보관되는 영역
    </pre>

    <h3>이벤트 루프 (event loop)</h3>
    <pre>
        - 콜 스택이 현재 실행 중인 실행 컨텍스트가 있는지, 태스크 큐에 대기 중인 함수가 있는지 반복 확인
        - 콜 스택이 비어있고 태스크 큐에 대기 중인 함수가 있다면 순차적(FIFO)으로 콜 스택으로 이동
        - 콜 스택으로 이동한 함수는 실행됨
    </pre>
</body>

 

👉🏻 interval

<body>
    <h1>02. interval</h1>

    <h3>setInterval</h3>
    <pre>
        setInterval(func|code[, delay, param1, param2, ...])
    </pre>

    <h3>clearInterval</h3>
    <pre>
        clearInterval(intervalId);
    </pre>
    
    <script>
        let count = 1;

        const intervalId = setInterval(() => {
            console.log(count);
            count++;

            if(count == 5) {
                clearInterval(intervalId);
            }
        }, 1000);
    </script>
</body>

 

  • asynchronous (비동기)

👉🏻 callback-hell

<body>
    <h1>콜백 지옥 (부제: 지옥 문턱만 밟아보기)</h1>

    <script>
        function increase(number, callback) {
            setTimeout(() => {
                const result = number + 10;

                if(callback) {
                    callback(result);
                }
            }, 1000);
        }

        // increase(0, result => console.log(result));

        increase(0, result => {
            console.log(result);
            increase(result, result => {
                console.log(result);
                increase(result, result => {
                    console.log(result);
                    increase(result, result => {
                        console.log(result);
                        increase(result, result => {
                            console.log(result);
                        })
                    })
                })
            })
        })
    </script>
</body>

 

👉🏻 promise

<body>
    <h1>Promise</h1>

    <pre>
        1. 비동기 처리 함수 실행 시 성패에 대한 처리가 용이하도록 resolve/reject 제공
          - resolve: 함수의 인자로 넘어온 값을 저장하고 있다가 then()을 만나면 저장한 값을 지닌 promise 객체 반환
          - reject: 어떠한 이유로 거부되어야 할 (조건일) 때의 promise 객체 반환
        2. promise 객체가 제공하는 메서드 사용 -> 콜백지옥 상황 해소
    </pre>

    <script>
        function increase(number) {
            const promise = new Promise((resolve, reject) => {
                setTimeout(() => {
                    const result = number + 10;

                    if(result > 50) {
                        const e = new Error('NumberTooBig');
                        return reject(e);
                    }

                    resolve(result);
                }, 1000);
            });

            return promise;
        }

        console.log(increase(0));

        increase(0)
            .then(number => {
                console.log(number);
                return increase(number);
            })
            .then(number => {
                console.log(number);
                return increase(number);
            })
            .then(number => {
                console.log(number);
                return increase(number);
            })
            .then(number => {
                console.log(number);
                return increase(number);
            })
            .then(number => {
                console.log(number);
                return increase(number);
            })
            .then(number => {
                console.log(number);
                return increase(number);
            })
            .catch(e => {
                console.log(e, "발생!!!!!!!!!");
            })
            .finally(() => {
                console.log("finally 실행!!!!!!!");
            });
    </script>
</body>

 

👉🏻 async - await

<body>
    <h1>async - await</h1>

    <pre>
        [ await의 기능 ]
        1. await가 달린 함수의 결과인 promise에 담긴 결과(promise 내부의 resolve에 담긴 결과)를 반환
            => then을 사용하지 않고 (메서드 체이닝 방식 없이) 비동기 처리 함수를 동기식 처리 가능
        2. await가 달린 비동기 처리들은 동기식으로 동작하게 함
    </pre>

    <script>
        function increase(number) {
            const promise = new Promise((resolve, reject) => {
                setTimeout(() => {
                    const result = number + 10;

                    if(result > 50) {
                        const e = new Error('NumberTooBig');
                        return reject(e);
                    }

                    resolve(result);
                }, 1000);
            });

            return promise;
        }

        async function run() {
            try {
                let result = await increase(0);
                console.log(result);

                result = await increase(result);
                console.log(result);

                result = await increase(result);
                console.log(result);

                result = await increase(result);
                console.log(result);

                result = await increase(result);
                console.log(result);

                result = await increase(result);
                console.log(result);
            } catch(e) {
                console.log(e + ' 에러 발생!!!');
            }
        }

        run();
    </script>
</body>

 

👉🏻 fetch

<body>
    <h1>fetch()</h1>

    <pre>
        - AJAX 외에 서버에 네트워크 요청을 보내고 받아올 수 있는 방법을 표준화한 api

        [ 기본 사용 방법 ]
        let promise = fetch(url, [options]);

        - url: 접근하고자 하는 url
        - options: 선택 매개변수로 http method, headers, body 내용을 객체로 지정 가능
                    (options를 지정하지 않으면 기본 GET 메서드로 요청)
    </pre>

    <script>
        async function callAPI() {
            const promise = fetch('https://jsonplaceholder.typicode.com/users');
            console.log(promise);
            // console.log(promise['[[PromiseResult]]']);  // 직접 접근 불가

            // 1. promise - then()
            // promise.then(response => {
            //     console.log(response);
            //     return response.json();
            // }).then(json => {
            //     console.log(json);
            // });

            const response = await promise;
            console.log(response);

            console.log(`응답 상태: ${response.status}`);
            console.log(`응답 헤더: ${response.headers}`);
            console.log(`본문 내용 사용 여부: ${response.bodyUsed}`);

            // text(): 결과로 넘어온 json 문자열을 그대로 가진 promise 객체 반환
            // const responseText = await response.text();
            // console.log(responseText);
            // console.log(JSON.parse(responseText));

            // 응답을 1회 받아 body 내용을 확인한 후에는 응답 body 내용에 다시 접근 불가

            // json(): 결과로 넘어온 json 문자열을 파싱한 promise 객체 반환
            const responseJson = await response.json();
            console.log(responseJson);
        }

        callAPI();
    </script>
</body>