-
[멋쟁이사자처럼] 프론트엔드 스쿨 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는 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. 프로그래머란?
- 프로그래머란 기술자다.
- 여러분의 손으로 표현해야 한다.
- 이해하고 끝내서는 안된다. 이론적인 면에서 그치지 않고, 배운 내용을 바탕으로 계속 만들어 봐야한다.
'Front-end 개발' 카테고리의 다른 글
[멋쟁이사자처럼] 프론트엔드 스쿨 7기 - 2차 이력서 특강 (2) 2023.08.25 [멋쟁이사자처럼] 프론트엔드 스쿨 7기 - 33일차 기록 및 복습 (DOM target, Class) (0) 2023.08.22 JS 클로저 - 캡슐화와 정보 은닉 (0) 2023.08.21 [이력서] 직무(JD)분석 - 카카오 (지도서비스/FE플랫폼팀) (0) 2023.08.20 [멋쟁이사자처럼] 주말 JS 기초 특강 코드 모음 - 230819 (0) 2023.08.19