ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [멋쟁이사자처럼] 프론트엔드 스쿨 7기 - 32일차 기록 및 복습 (DOM)
    Front-end 개발 2023. 8. 21. 11:53

    목차

    1. JS DOM

    2. 잡담


    1. JS DOM (Document Object Model)


    1-1. DOM 노드 요소 제어

    - DOM API를 이용하면 요소를 새롭게 생성하고, 위치하고, 제거할 수 있다.

    (1) document.createElement(target) : target 요소를 생성한다.

    (2) document.createTextNode(target) : target 텍스트를 생성한다.

    (3) document.appendChild(target) : target 요소를 element의 자식으로 위치한다.

    (4) document.removeChild(target) : element 의 target 자식 요소를 제거

    - 1~4 이후에 만들어진 새로운 메서드 (훨씬 편리하다)

    (5) document.append(target) : target 요소를 element 의 자식으로 위치한다. appendChild와 다른점은 노드 뿐만 아니라 여러 개의 노드를 한번에, 그리고 텍스트도 자식 노드로 포함시킬 수 있다는 것.

    (6) document.remove(target) : target 요소를 제거한다.

    - DOM (Document Object Model) 이라는 것은 노드를 트리형태로 만든 집합체

    - text는 요소 안에 들어가는 콘텐츠

    - 자식 요소를 넣으려면 필요할 때마다 각각 새로 만들어야 한다. (요소 대량생산할 때는 반복문을 사용한다)

    - createElement 또는 createTextNode 를 재사용할 수 없다. (노드는 1:1 매칭으로 한 요소에만 쓸 수 있다)

    - remove 는 removeChild 와 달리 부모와 자식을 모두 알 필요없이 지정해서 다 지울 수 있다.

    - append 는 여러개의 요소를 한번에 붙일 수 있다.

    - 아래 예제는 버튼을 누르면 ul의 자식요소로 li 가 자동으로 생성되는 예제이다.

    <body>
        <div class="parent"></div>
        <ul></ul>
        <button>생산시작!</button>
        
        <script>
            const myBtn = document.querySelector("button");
            const myUl = document.querySelector("ul");
    
            myBtn.addEventListener('click', function(){
                for(let i=0; i < 5; i++){
                    const myLi = document.createElement('li');
                    myUl.appendChild(myLi);
                }
            })
        </script>
    </body>

    - 스코프는 전역, 블록, 지역 스코프가 있다.

    - 반복문에서는 블록 스코프가 순회를 돌 때마다 새롭게 내부 요소가 생성되기 때문에 노드가 새로 생성되고 추가된다.

    <body>
        <div class="parent"></div>
        <ul></ul>
        <button>생산시작!</button>
        
        <script>
            const myBtn = document.querySelector("button");
            const myUl = document.querySelector("ul");
    
            myBtn.addEventListener('click', function () {
                for (let i = 0; i < 5; i++) {
                    const myLi = document.createElement('li');
                    const btnDel = document.createElement('button');
                    const btnTxt = document.createTextNode('버튼');
    
                    btnDel.append(btnTxt);
                    btnDel.addEventListener('click', () => {
                        myLi.remove();
                    });
                    myLi.append('삭제하기: ', btnDel);
                    myUl.appendChild(myLi);
                }
            });
        </script>
    </body>

    - element.append() 는 자식 요소의 제일 마지막에 붙는다.

    - 해당 요소의 앞에 붙이고 싶을 때는 element.insertBefore() 를 사용한다. 여기엔 두개의 인자가 필요하다.

    <body>
        <div id="parentElement">
            <span id="childElement">hello guys~</span>
        </div>
        <script>
            // parentElement.insertBefore(target, location); target요소를 parentElement의 자식인 location 위치 앞으로 이동합니다.
    
            var span = document.createElement("span");
            var sibling = document.getElementById("childElement");
            var parentDiv = document.getElementById("parentElement");
            parentDiv.insertBefore(span, sibling);
        </script>
    </body>

    - Element node 생성과 위치 지정을 한번에 할 수 있다.

    - element.textContent = myInput.value; 처럼 한번에 생성하고 위치키시는 것을 생략할 수 있다.

    - JS는 예전 코드는 그대로 두고 개발자들의 니즈를 반영하여 점점 새로운 기능을 추가한다.

    <body>
        <p></p>
        <input type="text">
        <button>Write Something!</button>
    
        <script>
            const myBtn = document.querySelector('button');
            const myP = document.querySelector('p');
            const myInput = document.querySelector('input');
    
            myBtn.addEventListener('click', function () {
                myP.textContent = myInput.value;
            })
        </script>
    </body>

    - input 요소에 이벤트를 달아서 바로바로 반영할 수도 있다.

    - element.innerHTML 을 통해 내부 HTML 구조를 반환할 수도 있고, 값을 할당할 수도 있다.

    - innerHTML 은 HTML 요소로 바꿀 수 있다면 HTML 요소를 넣고, 아니면 그저 text를 textNode로 넣어준다.

    - 기능은 비슷하긴 하지만 그냥 text 만 넣을거면 .textContent 를 사용하는 것을 권장한다.

    - innerHTML 은 요소(element) 내에 포함된 HTML 마크업을 가져오거나 설정합니다.

    - 여기서 중요한 점은 innerHTML로 값을 할당할 때, 마크업으로 변환할 수 있는 문자열이 있다면 마크업으로 만들어 보여준다는 것 입니다. 만약 그런 문자열이 없다면 그냥 문자열만 컨텐츠로 설정합니다.

    - innerText 속성은 요소의 렌더링된 텍스트 콘텐츠를 나타냅니다. (렌더링된에 주목하세요. innerText는 텍스트 내에 문법적으로 처리가 가능한 텍스트가 있으면 처리가 끝난 결과물을 텍스트로 전달합니다.)

    - textContent 속성은 노드의 텍스트 콘텐츠를 표현합니다. 컨텐츠를 단순히 텍스트로만 다룹니다.

    <body>
        <p></p>
        <input type="text">
        <button>Write Something!</button>
    
        <script>
            const myBtn = document.querySelector('button');
            const myP = document.querySelector('p');
            const myInput = document.querySelector('input');
    
            // input 요소에 'input' 이벤트를 연결하면 실시간으로 값이 반영되게 만들 수도 있습니다.
            myInput.addEventListener('input', function () {
                myP.textContent = myInput.value;
            })
            
            myP.innerHTML = "<strong>I'm Strong!!</strong>";
        </script>
    </body>

    - element.innerText의 쓰임을 element.textContent 와 비교하며 알아보자.

    - 기능상으로 보면 element.textContent 랑 매우 비슷하다.

    - textContent는 innerHTML과 달리 HTML 로 변환할 수 있는 문자열을 넣어도 HTML 요소로 변환하지 않고 그냥 text로 넣는다.

    - 모달을 넣어서 생성할 때 innerHTML로 넣는다. (createElement 보다 템플릿 리터럴(``)이 가독성이 높다.)

    - 이렇게 보면 element.textContent 랑 더욱 비슷해 보인다.

    <body>
        <h3>원본 요소:</h3>
        <p id="source">
            <style>
                #source {
                    color: red;
                }
            </style>
            아래에서<br />이 글을<br />어떻게 인식하는지 살펴보세요.
            <span style="display:none">숨겨진 글</span>
        </p>
        <h3>textContent 결과:</h3>
        <textarea id="textContentOutput" rows="6" cols="30" readonly>...</textarea>
        <h3>innerText 결과:</h3>
        <textarea id="innerTextOutput" rows="6" cols="30" readonly>...</textarea>
    
    
        <script>
            const source = document.getElementById("source");
            const textContentOutput = document.getElementById("textContentOutput");
            const innerTextOutput = document.getElementById("innerTextOutput");
    
            textContentOutput.innerHTML = source.textContent;
            innerTextOutput.innerHTML = source.innerText;
        </script>
    </body>

    textContent 와 innerText 코드 실행 결과

    - textContent는  HTML 내에서도 태그를 무시하고 순수한 Text 만 뽑아서 전달한다.

    - innerText 는 HTML 태그를 인식하고 CSS 꾸며주는 속성을 제외하고 HTML 기능을 적용시킨다. 단, CSS display 속성만을 인식한다. HTML 문서가 실제 화면에 표현(렌더링)된 텍스트 콘텐츠를 나타낸다.

    - innerText는 HTML을 인식하지만 CSS 는 일부 속성만 인식한다.

    - innerHTML은 보안상의 이유로 사용을 권장하지 않는다.

    - innerText는 이렇게 사용할 수 있다.

    <body>
        <figure>
            <img src="" alt="청바지 삼종세트">
            <figcaption>상품 가격 : 29,900원 <span class="hidden">VIP 회원이라면 29,900원!!</span></figcaption>
        </figure>
    
    
        <script>
            const cost = document.querySelector('figcaption');
            console.log(cost.textContent); // 모든 텍스트를 가져온다.
            console.log(cost.innerText); // HTML 을 인식하고 화면에 렌더링된 Text 만을 가져온다.
        </script>
    </body>

    - innerHTML 은 createElement 나 removeElement 등을 생략하고 사용하기 편리한데 주의할 점이 있다.

    - 보안적인 문제가 있다. MDN 에 의하면 innerHTML 이 웹 페이지에 텍스트를 삽입하는데 사용되는 것을 종종 볼 수 있데, 이것은 사이트의 공격 경로가 되어 잠재적인 보안 위험이 발생할 수 있다.

    const name = "John";
    // assuming 'el' is an HTML DOM element
    el.innerHTML = name; // harmless in this case
    
    // ...
    
    name = "<script>alert('I am John in an annoying alert!')</script>";
    el.innerHTML = name; // harmless in this case

    - innerHTML 이 HTML 을 인식하고 동작한다면, script 태그를 인식하고 정상 작동한다면 웹 페이지를 공격할 수 있다. 하지만 다행이도 innerHTML은 sciprt 태그를 인식하지 않는다.

    - 그러나 <script>요소를 사용하지 않고, 자바스크립트를 실행하는 방법이 있으므로, innerHTML을 사용하여 제어할 수 없는 문자열을 설정할 때 마다 여전히 보안위험이 있다.

    - 이미지가 에러가 났을 때 alert(1) 이 작동하도록 해둔 이벤트 코드이다.

    const name = "<img src='x' onerror='alert(1)'>";
    el.innerHTML = name; // shows the alert
    // el : element

    - 위의 내용을 통해 innerHTML이 의도치 않게 자바스크립트 코드를 실행시킬 수 있다는 것을 알게 되었습니다. 그럼 innerHTML은 사용해서는 안되는 속성일까요? 그렇지 않습니다. innerHTML은 템플릿 리터럴과 조합해 복잡한 HTML 구조도 동적으로 손쉽게 생성할 수 있다는 장점이 있습니다. 

    - 자바스크립트를 작동시킬 수 있는 가능성이 있으니 나쁜 의도를 가진 사용자가 코드를 입력 할 수 없도록 사용자의 입력 값을 innerHTML을 통해 할당 받는 일만 없도록 하면 안전하게 사용할 수 있습니다.

    - 권장 사항으로 innerHTML 대신 innerText 혹은 textContent 속성을 이용하자.

     

    1-2. JS 를 통해서 CSS 속성 제어하기

    - 자바스크립트를 사용하여 요소의 속성을 제어하는 방법은 다양하다.

    (1) 요소의 스타일을 제어하는 style 객체 (권장 X)

    (2) 속성에 접근하고 수정할 수 있는 Attribute 메소드

    (3) 요소에 데이터를 저장하도록 도와주는 data 속성

    - text.style.textAlign

    - text.style.color = 'blue';

    - 보통 현재 style 객체의 제거(초기화)에는 null 을 사용한다. target.style.color = null;

    - style 객체의 속성 식별자 규칙은 CSS 속성 이름을 대시의 경우 카멜케이스로 작성한다.

    - float 속성의 경우 이미 자바스크립트의 예약어로 존재하기 때문에 cssFloat 로 사용된다.

    - 하지만 style 로 속성을 수정하는 것은 CSS inline 스타일과 동일한 가중치를 가지므로 때문에 CSS를 통해 수정의 여지가 있는 스타일에는 많이 사용되지 않는 편입니다. 이럴 경우 classList를 이용한 클래스 제어가 더 효과적입니다.

    - Attribute 메소드를 사용하면 속성에 접근하고 수정할 수 있다.

     (1) getAttribute 메소드는 요소의 특정 속성 값에 접근할 수 있도록 한다.

     (2) setAttribute 메소드는 요소의 특정 속성 값에 접근하여 값을 수정한다.

    <body>
        <p id='myTxt'>hello lions</p>
        <div>
            <img
                src="https://static.ebs.co.kr/images/public/lectures/2014/06/19/10/bhpImg/44deb98d-1c50-4073-9bd7-2c2c28d65f9e.jpg">
        </div>
    
    
        <script>
            const target = document.querySelector('p');
            const myimg = document.querySelector('img');
            const idAttr = target.getAttribute('id');
            console.log(idAttr);
            myimg.setAttribute("src", "https://img.wendybook.com/image_detail/img159/159599_01.jpg");
    
            const div = document.querySelector('div');
            div.innerHTML = " <img src = 'https://img.wendybook.com/image_detail/img159/159599_01.jpg'> "
        </script>
    </body>

    - data-* 속성을 사용하면 HTML 요소에 추가적인 정보를 저장하여 마치 프로그램 가능한 객체처럼 사용할 수 있다.

    - 단, data 속성의 이름에는 콜론(:) 이나 영문 대문자가 들어가서는 안된다.

    - 아래 예제의 img 를 JS 객체처럼 활용할 수 있다.

    <img
        class="terran battle-cruiser"
        src="battle-cruiser.png"
        data-ship-id="324"
        data-weapons="laser"
        data-health="400"
        data-mana="250"
        data-skill="yamato-cannon"
    />
    <script>
        const img = document.querySelector('img')
        console.log(img.dataset);
        console.log(img.dataset.shipId);
    </script>

    - image.dataset.health 처럼 data 를 불러올 수 있다.

    - querySelctor 로 DOM tree 를 탐색하는 것은 자원낭비가 심하다. 그렇기 때문에 data-* 속성으로 요소에 저장해두고 dataset 으로 찾아 오는 것이 훨씬 쉽고 효율적이다. (HTML 요소에 데이터를 저장하고 불러오는 기능)

    <body>
        <ul>
            <li>
                <button data-name="redcola" data-cost="1000">
                    <img src="" alt="">
                    <span>redcola</span>
                    <strong>1000</strong>
                </button>
            </li>
        </ul>
    
        <script>
            const btn = document.querySelector('button');
            btn.addEventListener('click', function () {
                console.log(this.dataset.name, this.dataset.cost);
                console.log(btn.querySelector('span').textContent);
            })
        </script>
    </body>

    - (append, appendChild 외) 요소를 배치하는 또 다른 방법

    - 더 인접한 곳(Adjacent)으로 정밀하게 배치하기

    - insertAdjacentHTML : 요소 노드를 대상의 인접한 주변에 배치합니다.

    - begin 은 열린 태그, end 는 닫는 태그를 의미한다.

    - 'beforebegin' : 여는 태그 앞(이전)

    - 'afterbegin' : 여는 태그 바로 다음

    - 'beforeend' : 닫는 태그 이전

    - 'afterend' : 닫는 태그 바로 다음

    <strong class="sayHi">
        반갑습니다.
    </strong>
    const sayHi = document.querySelector('.sayHi');
    sayHi.insertAdjacentHTML('beforebegin', '<span>안녕하세요 저는</span>'); // 여는 태그 앞(이전)
    sayHi.insertAdjacentHTML('afterbegin', '<span>재현입니다</span>'); // 여는 태그 바로 다음
    sayHi.insertAdjacentHTML('beforeend', '<span>면접오시면</span>'); // 닫는 태그 이전
    sayHi.insertAdjacentHTML('afterend', '<span>치킨사드릴게요</span>'); // 닫는 태그 바로 다음

     

    1-3. DOM 안에서 노드 탐색하기

    -  firstElementChild 는 첫번째 자식 요소를 찾고, firstChild 는 첫번째 node 요소(개행문자도 포함)를 찾는다.

    - 주의할 것은 closest 는 자기 자신부터 시작해 가장 가까운 부모요소를 찾고 형제요소는 찾지 않는다.

    - 외울 필요없고 필요할 때 찾아서 쓰면 된다.

    <!-- 주석입니다 주석. -->
    <article class="cont">
        <h1>안녕하세요 저는 이런 사람입니다.</h1>
        <p>지금부터 자기소개 올리겠습니다</p>
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Deserunt incidunt voluptates laudantium fugit, omnis
        dolore itaque esse exercitationem quam culpa praesentium, quisquam repudiandae aut. Molestias qui quas ea iure
        officiis.
        <strong>감사합니다!</strong>
    </article>
    const cont = document.querySelector(".cont");
    console.log(cont.firstElementChild);  // 첫번째 자식을 찾습니다.
    console.log(cont.lastElementChild);   // 마지막 자식을 찾습니다.
    console.log(cont.nextElementSibling); // 다음 형제요소를 찾습니다.
    console.log(cont.previousSibling);    // 이전 형제노드를 찾습니다.
    console.log(cont.children);           // 모든 직계자식을 찾습니다.
    console.log(cont.parentElement);      // 부모 요소를 찾습니다.
    // 자기 자신부터 시작해 부모로 타고 올라가며 가장 가까운 cont 클래스 요소를 찾습니다. 단, 형제요소는 찾지 않습니다.
    console.log(cont.querySelector('strong').closest('.cont').innerHTML);​

     

    1-4. 실습

    - 우리가 한 실습은 기초

    See the Pen Select-box Basic by redcontroller (@redcontroller) on CodePen.

     

    - 선택자는 최대한 좁은 범위를 탐색하도록 지정해야 한다.

    - 성능을 고려해야 하는 경우는, 메뉴 같은 것들이 나오는 속도가 느리면 고려해야 한다.

    - 웹 접근성까지 고려한 정답 (중급 이상)

    See the Pen Select-box Advance by redcontroller (@redcontroller) on CodePen.

     

    1-5. 이벤트 객체와 흐름 (DOM 의 꽃  : 면접 단골 질문)

    - 이벤트에 호출되는 핸들러(addEventListener 의 콜백함수)에는 이벤트와 관련된 모든 정보를 가지고 있는 매개변수가 전송된다. 이것이 바로 이벤트 객체.

    <article class="parent">
        <ol>
            <li><button class="btn-first" type="button">버튼1</button></li>
            <li><button type="button">버튼2</button></li>
            <li><button type="button">버튼3</button></li>
        </ol>
    </article>
    const btnFirst = document.querySelector('.btn-first');
    btnFirst.addEventListener('click', (event) => {
        console.log(event);
    });

    - 브라우저 화면에서 이벤트가 발생하면 브라우저는 가장 먼저 이벤트 대상을 찾기 시작한다.

    - 브라우저가 이벤트 대상을 찾아갈 때는 가장 상위 window 객체부터 html, body 순으로 DOM 트리를 따라 내려간다. 결국 타겟까지 내려간다. 이를 캡처링 단계라고 한다.

    - 캡처링 이벤트: 부모 DOM 에서 자식 DOM 으로 타고 내려가면서 이벤트를 찾아 나가는 과정

    - 캡처링 과정에서 실행되는 이벤트를 캡처링 이벤트라고 한다.

    - 캡처링 이벤트를 사용하는 방법은 콜백 함수 다음으로 true 를 주면 된다.

    - 이벤트가 발생한 자식 DOM 에서 최상위 DOM 까지 타고 올라가는 과정을 버블링 이벤트 (돌고래 초음파 처럼 찾는다)

    - 바다 속에서 방울이 보글보글 올라가는 이미지다.

    - 리스너가 차례로 실행되는 것을 이벤트 전파(event propagation) 라고 한다.

    - 버블링 이벤트를 사용하는 방식은 콜백 함수 다음으로 false 를 주면 된다. (default 값은 false, 버블링 이벤트)

    이벤트 흐름

    - window, document, parent 에 캡처링 이벤트가 달려있다. (true)

    - btnFirst, parent, document, window 에 버블링 이벤트가 달려있다. (default === false)

    <!DOCTYPE html>
    <html lang="ko">
    
    <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>Document</title>
        <link rel="stylesheet" href="../reset.css">
        <style>
        </style>
    </head>
    
    <body>
        <article class="parent">
            <button class="btn" type="button">버튼</button>
        </article>
    
        <script>
            const parent = document.querySelector('.parent');
            const btnFirst = document.querySelector('.btn');
            btnFirst.addEventListener('click', (event) => {
                console.log("btn capture!");
            })
    
            window.addEventListener('click', () => {
                console.log("window capture!");
            }, true); // true : 캡처링 단계의 이벤트가 발생하도록 합니다.
    
            document.addEventListener('click', () => {
                console.log("document capture!");
            }, true);
    
            parent.addEventListener('click', () => {
                console.log("parent capture!");
            }, true);
    
            btnFirst.addEventListener('click', (event) => {
                console.log("btn bubble!");
            })
    
            parent.addEventListener('click', () => {
                console.log("parent bubble!");
            });
    
            document.addEventListener('click', () => {
                console.log("document bubble!");
            });
    
            window.addEventListener('click', () => {
                console.log("window bubble!");
            });
        </script>
    </body>
    
    </html>

    - 첫번째 addEnventListener 는 default 값으로 bubling Event 이므로 캡처링 이벤트가 모두 실행 후 실행된다.

    - 이벤트 흐름은 크롬이건 사파리건 똑같다.

    - 하지만 form 내부의 button 의 경우 둘 사이의 관계는 강제되어 있다.
    - form 태그를 캡쳐링으로 변경해도 버튼보다 늦게 실행되게 된다.
    - 이것은 form 이 true 를 줌으로써 캡처링으로 변경해도 버블링으로 실행될 것이라고 본다.

    <body>
        <form action="">
            <input type="text">
            <button>버튼!</button>
        </form>
    
        <script>
            const form = document.querySelector('form');
            const btn = document.querySelector('button');
    
            form.addEventListener('submit', (event) => {
                event.preventDefault(); // 새로 고침을 막는다.
                console.log('hello submit');
            }, true); // 캡처링 이벤트임에도 불구하고 button 보다 늦게 실행된다.
            // submit 은 무조건 버블링으로 실행될 것이다.
    
            btn.addEventListener('click', () => {
                console.log('hello button');
            });
        </script>
    </body>

    2. 잡담

    2-1. 프로그래머란?

    - 프로그래머란 기술자다.

    - 여러분의 손으로 표현해야 한다.

    - 이해하고 끝내서는 안된다. 이론적인 면에서 그치지 않고, 배운 내용을 바탕으로 계속 만들어 봐야한다.

Designed by Tistory.