1. DOM (Document Object Model)

DOM API란?
- HTML 문서의 내용을 트리형태로 구조화하여 웹페이지와 프로그래밍 언어를 연결시켜주는 역할
- 노드(node) : 각각의 요소와 속성, 콘텐츠를 표현하는 단위
DOM 트리에 접근하기
- document : 브라우저가 불러온 웹페이지 / DOM 트리의 진입점
- document 객체를 통해 HTML 문서에 접근이 가능하다.
// 해당하는 Id를 가진 요소에 접근하기 document.getElementById(); // 해당하는 모든 요소에 접근하기 document.getElementsByTagName(); // 해당하는 클래스를 가진 모든 요소에 접근하기 document.getElementsByClassName(); // css 선택자로 단일 요소에 접근하기 // 페이지에 있는 selector 태그중 가장 앞에 나오는 태그만 나온다 document.querySelector("selector"); // css 선택자로 여러 요소에 접근하기 document.querySelectorAll("selector");
DOM 제어 명령어
- 이벤트 삽입
- target.addEventListener( type, listener )의 문법 형태를 지닌다.
- 이벤트 MDN 설명
<button>HELLO!</button>
// 이벤트의 타입에는 click, mouseover, mouseout, wheel 등 다양한 이벤트를 감지합니다. // listener 함수의 인수에는 이벤트에 대한 정보가 담겨있습니다. const myBtn = document.querySelector("button"); myBtn.addEventListener('click', function(){ console.log("hello world"); })
- 클래스 제어
- classList 객체를 통해 요소의 class 속성을 제어할 수 있다.
<button>Make me BLUE!</button>
const myBtn = document.querySelector("button"); myBtn.addEventListener('click', function(){ // blue 라는 클래스의 속성 값을 지정 할 수 있습니다. myBtn.classList.add("blue"); // myBtn.classList.remove("blue"); 클래스를 제거합니다. // myBtn.classList.toggle("blue"); 클래스를 토글합니다. 없으면 넣어주고, 있으면 제거합니다. // myBtn.classList.contains("blue"); 해당하는 클래스가 있는지 확인합니다. })
- classList 객체를 통해 요소의 class 속성을 제어할 수 있다.
- JavaScript 문자열을 사용해 element, text 노드를 생성하거나 추가
- 요소 안의 값에 접근하여 값을 가져오거나, 변경 가능
<p></p> <input type="text"> <button>Write Something!</button>
const myBtn = document.querySelector("button"); const myP = document.querySelector("p"); const myInput = document.querySelector("input"); myBtn.addEventListener('click', function(){ myP.textContent = myInput.value; }); // input 요소에 'input' 이벤트를 연결하면 실시간으로 값이 반영되게 만들 수도 있습니다. myInput.addEventListener('input', ()=>{ myP.textContent = myInput.value; }); myP.innerHTML = "<strong>I'm Strong!!</strong>"; // innerHTML 은 요소(element) 내에 포함된 HTML 마크업을 가져오거나 설정합니다. 중요한 기능은 innerHTML로 값을 할당할 때, 마크업으로 변환할 수 있는 문자열이 있다면 마크업으로 만들어 보여준다는 것 입니다. 만약 그런 문자열이 없다면 그냥 문자열만 컨텐츠로 설정합니다. // innerText 속성은 요소의 렌더링된 텍스트 콘텐츠를 나타냅니다. (렌더링된에 주목하세요. innerText는 텍스트 내에 문법적으로 처리가 가능한 텍스트가 있으면 처리가 끝난 결과물을 텍스트로 전달합니다.) // textContent 속성은 노드의 텍스트 콘텐츠를 표현합니다. 컨텐츠를 단순히 텍스트로만 다룹니다.
💡innerHTML 더 생각해보기
위의 내용을 통해 innerHTML이 의도치 않게 자바스크립트 코드를 실행시킬 수 있다는 것을 알게 되었습니다. 그럼 innerHTML은 사용해서는 안되는 속성일까요?
그렇지 않습니다. innerHTML은 템플릿 리터럴과 조합해 복잡한 HTML 구조도 동적으로 손쉽게 생성할 수 있다는 장점이 있습니다.
단, 자바스크립트를 작동시킬 수 있는 가능성이 있으니 나쁜 의도를 가진 사용자가 코드를 입력 할 수 없도록 사용자의 입력 값을 innerHTML을 통해 할당 받는 일만 없도록 하면 안전하게 사용할 수 있습니다. 대신 innerText 혹은 textContent 속성을 이용합시다.Element.innerHTML - Web API | MDNElement (en-US) 속성(property) innerHTML 은 요소(element) 내에 포함 된 HTML 또는 XML 마크업을 가져오거나 설정합니다.https://developer.mozilla.org/ko/docs/Web/API/Element/innerHTML#security_considerations
💡innerText 더 생각해보기
innerText 속성은 요소의
렌더링된 텍스트 콘텐츠를 나타낸다고 합니다. 표현이 애매한데요, mdn 에 따르면 innerText는 사용자가 커서를 이용해 요소의 콘텐츠를 선택하고 클립보드에 복사했을 때 얻을 수 있는 텍스트의 근삿값을 제공한다고 합니다.
이 말은 즉, 예를 들어서 네이버 메인페이지의 특정 텍스트를 복사에서 메모장에 붙여넣기한다고 가정했을때, 텍스트의 줄 바꿈같은 것은 가져오지만 폰트의 굶기나 색상까지 가져오지 않는 것과 같다고 할 수 있겠습니다.따라서 innerText는 display:none으로 인해 제거되는 텍스트, br 태그를 통해 줄 바꿈되는 텍스트의 '형태' 는 인식하지만 color 나 굵기, 사이즈 등 과 같은 정보는 인식하지 못한다고 볼 수 있습니다.
- 요소 안의 값에 접근하여 값을 가져오거나, 변경 가능
- 속성 제어
- 요소의 스타일을 제어하는 style 객체
- 이 방식은 inline 이 되므로 다른 스타일에 악영향을 줄 수 있다.
- 따라서 가급적 사용 X
- 클래스(ClassList)를 통해서 적용하는 것이 가장 좋다
- 속성에 접근하고 수정할 수 있는 Attribute 메소드
- getAttribute : 요소의 특정 속성 값에 접근
- setAttribute : 요소의 특정 속성 값을 수정
<p id='myTxt'>hello lions</p> <img src='https://static.ebs.co.kr/images/public/lectures/2014/06/19/10/bhpImg/44deb98d-1c50-4073-9bd7-2c2c28d65f9e.jpg'> <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"); </script>
- 요소에 데이터를 저장하도록 도와주는 data 속성
- HTML 요소에 추가적인 정보를 저장하여 마치 프로그램 가능한 객체처럼 사용할 수 있게 한다.
<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>
- HTML 요소에 추가적인 정보를 저장하여 마치 프로그램 가능한 객체처럼 사용할 수 있게 한다.
- 더 인접한곳(Adjacent)으로 정밀하게 배치하기
- insertAdjacentHTML : 요소 노드를 대상의 인접한 주변에 배치
<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>');
- DOM 안에서 노드 탐색하기
<!-- 주석입니다 주석. --> <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.childNodes); // 모든 자식노드를 찾습니다. console.log(cont.parentElement); // 부모 요소를 찾습니다. // 자기 자신부터 시작해 부모로 타고 올라가며 가장 가까운 cont 클래스 요소를 찾습니다. 단, 형제요소는 찾지 않습니다. console.log(cont.querySelector('strong').closest('.cont').innerHTML);
- 이벤트 객체
<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 객체부터 DOM트리를 따라 내려가는 과정
- 버블링 단계 : 이벤트 대상을 찾고 캡처링이 끝난후, 다시 DOM 트리를 따라 올라가며 만나는 모든 버블링 이벤트 리스너를 실행
- 이벤트 전파 : 이 과정에서 이벤트 리스너가 차례로 실행되는 것
<!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'); //default : false 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>
- 이벤트 target, currentTarget
- target : 이벤트가 발생한 진원지의 정보 / 이벤트 리스너가 없는 요소의 이벤트가 발생했을때도 해당 요소에 접근 가능
- currentTarget : 이벤트 리스너가 연결된 요소를 참조
<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> <script> const parent = document.querySelector('.parent'); parent.addEventListener('click', function (event) { console.log(event.target); console.log(event.currentTarget); }) </script>
- 이벤트 위임 (테크닉 - 필수적인건 아님)
<!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"> <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> <script> const parent = document.querySelector('.parent'); parent.addEventListener('click', function (event) { console.log(event.target); if (event.target.nodeName === "BUTTON") { event.target.textContent = "버튼4"; } }) </script> </body> </html>
- 자손 요소들에게 이벤트 리스너가 적용된것처럼 작동이 가능하다.
- 이벤트의 this
- 이벤트 리스너 함수 내부에서의 this는 이벤트가 연결된 노드를 참조
- event.currentTarget과 유사
- 이벤트 리스너 함수를 화살표 함수로 쓴다면 this가 가리키는 대상이 달라진다!
- 화살표 함수의 this : 함수를 감싸고 있는 상위공간을 가리킨다.
<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> <script> const parent = document.querySelector('.parent'); parent.addEventListener('click', function (event) { console.log(this); }); const myObj = { name: 'jaehyun', walk() { parent.addEventListener('click', () => { console.log(this.name + ' is walking'); }) } } </script>
- preventDefault()
- 브라우저의 기본 이벤트 동작을 취소
- 자바스크립트를 통해 기능을 처리하고자 할때 사용
<!-- 앵커의 기본 동작을 중지 --> <a href="https://www.naver.com" class="link">네이버 링크입니다만..</a> <script> const link = document.querySelector('.link'); link.addEventListener('click', (event) => { console.log('clicked'); event.preventDefault(); }) </script> <!-- submit 의 기본 동작을 중지 --> <form action=""> <button type="submit" class="submit">제출</button> </form> <script> const submit = document.querySelector('.submit'); submit.addEventListener('click', (event) => { console.log('clicked'); event.preventDefault(); }) </script>
- stopPropagation
- 이벤트의 전파과정을 차단
- 부모자식 관계에 있는 요소들이 둘다 eventlistener가 달려있다면, 부모의 이벤트가 자식 이벤트에 의해 발생하는 경우를 막을 때 사용
- 요소의 스타일을 제어하는 style 객체
Ajax
동기 vs 비동기
- 동기 : 코드의 순서에 따라 코드가 처리되는 것
- 비동기 : 코드의 순서와 별도로 동시에 코드 처리가 진행되는 것
- 보통 언제 발생할지 모르는 일을 처리할때 비동기를 사용한다.
console.log(1);
// setTimeout으로 콜백함수가 일정시간(100ms) 뒤에 실행하도록 코드를 작성합니다. 순서대로 실행되지 않습니다.(비동기적으로 실행). 이러한 비동기 실행 코드는 setInterval, addEventListener 와 같은 함수들이 있습니다.
setTimeout(() => console.log(2), 100);
[3, 4, 5].forEach(i => console.log(i));
console.log(6);
- setInterval() : https://developer.mozilla.org/en-US/docs/Web/API/setInterval
- addEventListener도 비동기 방식이다
- event가 언제 발생할지 모르기 때문에
XMLHttpRequest (XHR)
- 서버와의 비동기 통신을 가능하게 하는 여러 기능들을 가진 자바스크립트 객체
// XHR 객체를 생성합니다.
const requestObj = new XMLHttpRequest();
requestObj.open('GET', 'url'); // 요청을 초기화합니다. 통신방법과 요청을 발신할 대상의 주소를 전달합니다.
requestObj.onreadystatechange = () => { // readystate 가 변화하면 실행되는 이벤트리스너 입니다.
// readystate : 요청을 보내는 클라이언트의 상태를 의미합니다.
// readystate의 종류
// 0 (UNSENT) - XHR 객체가 생성되었지만 아직 초기화되지 않았습니다.
// 1 (OPENED) - open()함수가 호출되어 요청이 초기화되었습니다.
// 2 (HEADERS_RECEIVED) - send()함수가 호출되었습니다.
// 3 (LOADING) - 데이터를 다운받는 중 입니다.
// 4 (DONE) - 통신이 완료되었습니다.
if (requestObj.readyState == 4 && requestObj.status == "200") {
const result = requestObj.responseText;
}
};
requestObj.send(); // 서버로 요청을 보냅니다. send 메소드가 실행되어야만 우리가 위에서 설정한 내용들이 의미를 가지게 됩니다.
- callback 지옥
- callback 함수 : 함수의 인자로 들어오는 함수
- callback 함수가 끊이지 않는걸 의미한다.
- 비동기 코드를 동기코드와 같이쓰는 것은 문제가 발생할 수 있다.
- 실행 순서를 보장할 수 없기 때문에
// sudo코드 const result = 비동기통신함수(); const result2 = 비동기통신함수2(); const total = result + result2; // 이런 방식은 불가능합니다. result와 result2에 무슨 값이 들어있을지 생각해보세요.
- 비동기 방식에서 실행 순서를 보장하기위해 callback함수를 활용할 수 밖에 없고, callback 지옥에 빠지게 된다.
const total = 비동기통신함수( input, 통신함수의결과를가공하는함수1 ( result, 비동기통신함수2( 통신함수의결과를가공하는함수2( result, result2 ) ) ) ); // 이렇게 비동기 함수가 끝나기 전에 중간 중간에 필요한 콜백함수를 실행시키며 사용할 수 밖에 없었습니다.
예시코드
<script> 'use strict'; class UserStorage { // 사용자가 입력한 값과 유저 정보가 일치하는 유저를 찾습니다. searchUser(userName, password, onSuccess, onError) { const requestObj = new XMLHttpRequest(); requestObj.open('GET', 'users.json'); // 요청 초기화 requestObj.onreadystatechange = () => { if (requestObj.readyState === 4 && requestObj.status === 200) { const result = JSON.parse(requestObj.responseText).user.find((item) => { return item.userName === userName && item.password === password }); // 사용자가 입력한 정보와 일치하는 유저가 존재한다면 if (result) { onSuccess(userName); // 사용자가 입력한 정보와 일치하는 유저가 존재하지 않는다면 } else { onError(new Error('user not found')); } } } requestObj.send(); } // 유저에 맞는 인사말을 출력합니다. sayHi(user, onSuccess, onError) { const requestObj = new XMLHttpRequest(); requestObj.open('GET', 'greetings.json'); // 요청 초기화 requestObj.onreadystatechange = () => { if (requestObj.readyState === 4 && requestObj.status === 200) { const result = JSON.parse(requestObj.responseText).greetings.find((item) => { return item.userName === user }); // 사용자가 입력한 유저 이름과 일치하는 인사말이 존재한다면 if (result) { onSuccess(result); // 사용자가 입력한 유저 이름과 일치하는 인사말이 존재하지 않는다면 } else { onError(new Error('no greetings')); } } } requestObj.send(); } } const userStorage = new UserStorage(); const userName = prompt('이름을 입력하세요.'); const password = prompt('비밀번호를 입력하세요.'); userStorage.searchUser( userName, password, (name) => { userStorage.sayHi( name, (result) => { alert(`당신에게 인사합니다! ${result.userName}님 ${result.greetings}`); }, (error) => { console.log(error); }); }, (error) => { console.log(error); } ); </script>
Fetch API
- XMLHttpRequest를 대체할 새로운 API
- XHR과의 차이점 : fetch는 인스턴스를 만들지 않고, ‘약속’을 반환한다.
// 커피를 주문하는 프로미스 객체를 생성합니다. 생성자에는 약속을 지키기 위한 resolve와, 약속을 지키지 못했을 때를 대비한 reject 두 가지를 인자로 전달합니다. // 프로미스 객체를 생성하는 순간 프로미스 생성자함수의 콜백 함수가 실행됩니다. 이를 실행자(executor)라 부릅니다. const orderCoffee = new Promise((resolve, reject) => { const requestObj = new XMLHttpRequest(); requestObj.open('GET', 'orderCoffee.txt'); requestObj.onreadystatechange = () => { if (requestObj.readyState === 4) { if (requestObj.status === 200) { const result = requestObj.responseText; // resolve 메소드가 실행되면 then 메소드가 자동으로 호출됩니다. resolve(result); } else { // resolve 메소드 호출이 없는 상태에서 reject 메소드가 실행되면 catch 메소드가 자동으로 호출됩니다. reject(new Error(`커피주문이 정상적으로 이뤄지지 않았습니다.: ${requestObj.status}`)); } } }; requestObj.send(); }); // 이 부분에 주목해주세요. then 메소드를 사용하면 비동기 코드를 마치 동기적인 코드처럼 작성할 수 있습니다. 앞에서 작성한 XHR 코드와 비교해보는것도 좋습니다. // resolve 메소드가 실행될때 전달된 인자는 then 메소드의 콜백함수의 인자로 전달됩니다. orderCoffee.then((asyncResult) => { console.log(asyncResult); console.log('약속이 이루어졌습니다.'); return asyncResult; }).catch((error) => { // then 메소드는 프라미스 객체를 반환하기 때문에 catch 메소드를 이어서 쓰는것이 가능합니다. // resolve 메소드와 마찬가지로 reject 메소드가 실행될때 전달된 인자는 catch 메소드의 콜백함수의 인자로 전달됩니다. console.log(error); })
- Promise란?
- 무언가를 할 것이라 미리 정하는 행위
- 프로미스 객체는 약속을 지키기 위한 resolve와 약속을 지키지 못했을때를 대비한 reject 두가지를 인자로 전달한다.
- resolve : 데이터 통신이 성공 했을때 실행
- reject : 데이터 통신이 실패 했을때 실행
- 서버와 통신이 성공했을때 클라이언트에서 처리해야하는 내용이 then 뒤에 나옴
- 반대로 서버와 통신이 실패했을때 클라이언트에서 처리해야하는 내용은 catch 뒤에 나옴
async, await
- async 키워드는 어떤 함수든 프로미스 객체를 반환하게 만들 수 있다.
- await는 async 함수 안에서 promise 객체의 상태가 결정될 때까지 다음 코드를 대기시킨다. 그리고 프로미스 객체의 fulfilled 값을 반환한다.
- await는 반드시 async 함수 안에서만 사용가능 하다.
async function message() {
const hello = await new Promise((resolve) => {
setTimeout(() => {
resolve('hello');
}, 100)
})
const world = await new Promise((resolve) => {
setTimeout(() => {
resolve('world');
}, 100)
})
console.log(`${hello} ${world}`);
}
message();
'Dev > ESTsoft 오르미' 카테고리의 다른 글
강사님 코드리뷰 (0) | 2024.02.22 |
---|---|
공부한 기술들 (0) | 2024.02.22 |
JavaScript2 (타입 ~) (0) | 2024.02.22 |
JavaScript1 (~조건문과 반복문) (0) | 2024.02.22 |
CSS2 (Position & Flex) (0) | 2024.02.22 |