웹 프론트엔드
자바스크립트5 - DOM(Document Object Model)
토리쟁이
2024. 1. 14. 01:45
이번 포스팅에서는 DOM에 대해 공부해 보고자 한다.
DOM(Document Object Model)
- 문서 객체 모델
- HTML 문서 요소의 집합
- HTML 문서는 각각의 node와 object의 집합으로 문서를 표현하는데, 이러한 각각의 node 나 object에 접근하여 문서 구조, 스타일, 내용 등을 변경하고자 할 때 사용하는 객체
- 즉, 웹 페이지의 콘텐츠 및 구조, 그리고 스타일 요소를 구조화시켜 표현하여 프로그래밍 언어가 해당 문서에 접근하여 읽고 조작할 수 있도록 API를 제공하는 일종의 인터페이스
- DOM은 웹 페이지, 즉 HTML 문서를 계층적 구조와 정보로 표현하며, 이를 제어할 수 있는 속성과 메서드를 제공하는 트리 자료구조
원하는 요소를 변경시키기 위해서는 그 대상 요소를 가지고 올 수 있어야 한다.
이를 위한 여러 가지 요소 선택 방법들이 존재한다.
예제를 위한 html 코드는 다음과 같다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM</title>
</head>
<body>
<div class="container">
<div class="box" id="green">1</div>
<div class="box" id="red">2</div>
<div class="box">3</div>
<div class="box">4</div>
<div class="box">5</div>
</div>
<input type="text" name="userName">
<input type="text" name="userName">
<!--요소 다루기-->
<div id="parent">
Text
<div id="two">div 두번째 Text</div>
<div>div 세번째 Text</div>
</div>
</body>
</html>
요소 선택
- querySelector("요소 선택자"): 요소 선택자와 일치하는 문서 내 첫 번째 요소 반환
- class: .
- id: #
- 태그: 태그명
- querySelectorAll( "요소 선택자"): 요소 선택자와 일치하는 문서 내 모든 요소 반환
- getElementById("ID 이름"): 해당 id를 가지고 있는 요소를 반환(맨 처음 등장하는 요소만 반환하며, id값은 고유하게 작성해야 하므로 하나만 있는 것이 당-연)
- getElementsByClassName("class 이름"): 해당 클래스 이름을 가진 요소를 반환
- 반환값: HtmlCollection
- getElementsByTagName('태그명'): 해당 태그인 모든 요소를 반환
- 반환값: HtmlCollection
- getElementsByName('name명'): 해당 name명과 같은 모든 요소를 반환
- 반환값: NodeList
- 주로 input의 속성으로 자주 사용되는 name=""을 이용
위의 설명을 보면, 해당 함수의 반환값이 HtmlCollection인 경우와 NodeList인 경우가 각각 있다는 것을 알 수 있다.
그 두 경우의 차이점은 다음과 같다.
HtmlCollection VS NodeList
- HtmlCollection 객체는 태그 요소만 포함하는 반면, NodeList 객체는 태그와 텍스트 노드 등을 모두 포함한다.
- HtmlCollection 객체는 자식 요소 노드에 접근할 때, 인덱스 방식 & namedItem 방식을 모두 사용할 수 있지만, NodeList 객체는 인덱스로만 접근이 가능하다.
- HtmlCollection 객체는 동적이고, NodeList 객체는 정적이다.(아래 설명 참고)
- HtmlCollection 객체는 DOM을 다루면서 새로운 요소가 추가됐을 때, 추가된 요소도 가져올 수 있지만, NodeList 객체는 새로 추가된 요소를 가져올 수 없다.
- HtmlCollection 객체는 배열과 유사하지만, 실제 배열이 아니다. but, NodeList 객체는 배열이기 때문에 배열만의 사용 가능한 메소드(forEach 등..)를 사용할 수 있다.
let parent = document.getElementById('parent');
let parentChildren = parent.children; //HtmlCollection 반환
let parentChildNodes = parent.childNodes; // NodeList 반환
console.log('children:', parentChildren);
console.log('children:', parentChildren.namedItem('two')); // NodeList는 안되지만, HtmlCollection은 이렇게 접근이 가능함
console.log('ChildNodes:', parentChildNodes);
// 자식 추가
// 추가할 부모 선택 -> 추가할 자식 생성 -> 자식 추가
// 위에서 추가할 부모를 선택했으므로, 추가할 자식 생성을 해보자
let child1 = document.createElement('div'); // <div></div> 생성
child1.innerText = '네번째 Text'; // 태그 안 내용
parent.appendChild(child1); // 자식 추가
요소 다루기
태그 내부 내용 변경
- 아래의 3가지 방법들은 모두 태그 내에 들어갈 문자열을 지정한다는 공통점이 있지만, 각각의 차이점도 존재한다.
- innerText: 문자열을 넣되, 해당 문자열의 공백이 제거됨
- innerContent: 문자열을 넣되, 해당 문자열의 공백이 제거되지 X
- innerHTML: HTML 요소를 넣는 것 (→ 태그를 작성하면 반영되어 나타남)
let two = document.getElementById('two');
//two.innerHTML = '<strong>강조</strong>';
//two.innerHTML = '강조'; // 기존에 있던 값들이 없어지므로 윗줄 소용x
two.innerText = ' textContent<strong>강조</strong> @ !!!!!!! ** ';
two.textContent = ` textContent<strong>강조</strong>.
//@ !!!!!!! ** `
console.log(two.innerText); // 공백 제거
console.log(two.textContent); // 공백 제거 x
클래스 관련 메소드 classList~
- 선택 요소에 class를 추가/삭제/존재여부 확인하는 메소드들
- 요소.classList.add(): 선택 요소에 클래스 추가
- 요소. classList.remove(): 선택 요소에 클래스 삭제
- 요소. classList.contains(): 선택 요소에 클래스 유무 확인 → true/false 반환
- 요소. classList.toggle(): 클래스가 있으면 제거, 없으면 추가
greenBox.classList.add('box1'); // 클래스 추가
greenBox.classList.remove('box1'); // 클래스 삭제
console.log(greenBox.classList.contains('box1')); // 클래스 여부 확인
greenBox.classList.toggle('d-none'); // 해당 클래스가 있으면 제거 없으면 추가
// toggle을 이용한 예제 문제
// 현재 문제에서는 완료한 일이 todo 클래스로 되어 있고, 해야할 일이 done 클래스로 되어 있다.
// 바꿔치기 ㄱㄱ
let lists = document.getElementsByTagName('li');
for (let li of lists) {
li.classList.toggle('done')
li.classList.toggle('todo')
console.log(li)
}
속성 변경 메소드
- 요소.setAtrribute("속성명", "지정할 속성")
- 선택요소.해당 요소가 갖는 속성 = "원하는 속성값"
// 속성 변경 메소드
let inputE1 = document.getElementById('userName');
inputE1.setAttribute('placeholder', 'name');
inputE1.placeholder = 'name'; // 위의 코드와 동일
inputE1.style = 'color:red';
inputE1.style.color = 'red'; // 위의 코드와 동일
다른 노드에 접근하기
- 특정 노드를 선택한 후, 해당 노드의 형제/부모/자식 노드에 접근 가능
- 요소.children
- 요소.parentNode: 특정 노드의 부모 요소
- 요소.previousElementSibling: 특정 노드의 이전 형제 '요소' 반환
- 요소.previousSibling: 특정 노드의 이전 형제 반환(위 코드와 동일한데 차이점이 있음 말로 설명하기 어려우니 실습 코드 참고할 것)
- 요소.nextElementSibling: 특정 요소의 다음 형제 '요소' 반환
- 요소.nextSibling: 특정 요소의 다음 형제 반환
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM 다른 노드 접근하기</title>
</head>
<body>
<div class="container">
<div class="box" id="green">1</div>
previous 테스트
<!--주석 previous 테스트-->
<div class="box" id="red">2</div>
next 테스트
<div class="box">3</div>
<div class="box">4</div>
<div class="box">5</div>
</div>
<button onclick="getParent()">getParent</button>
<button onclick="pElSibling()">pElSibling</button>
<button onclick="pNoSibling()">pNoSibling</button>
<button onclick="nElsibling()">nElsibling</button>
<button onclick="nNosibling()">nNosibling</button>
<script>
let container = document.getElementById('container');
let greenBox = document.getElementById('green');
let redBox = document.getElementById('red');
// parentNode: 부모 요소를 가져온다
function getParent()
{
console.log(greenBox.parentNode);
}
// previousElementSibling vs previousSibling 차이점 공부하기
// 이전 형제 '요소' 반환
function pElSibling()
{
console.log(redBox.previousElementSibling); //<div class="box" id="green">1</div>
}
// 바로 앞에 있는 형제 반환(dom tree 기준)
// 앞에 있는 것이 주석이거나 비어있으면 #text라고 뜸
function pNoSibling(){
console.log(redBox.previousSibling); //#text
}
// 다음 형제 '요소' 반환
function nElsibling(){
console.log(redBox.nextElementSibling); // <div class="box">3</div>
}
// 다음 형제 반환
function nNosibling(){
console.log(redBox.nextSibling); //"next 테스트"
}
</script>
</body>
</html>
요소의 추가&삭제
- 요소.createElement('태그명'): 해당 태그를 갖는 요소를 생성
- 요소.append() / 요소.appendChild(): 선택된 요소의 맨 뒤의 자식 요소로 추가
- 요소.prepend(): 선택된 요소의 맨 앞에 자식 요소로 추가
- 요소.before(): 선택된 요소의 앞에 형제 요소로 추가
- 요소.after(): 선택된 요소의 바로 뒤에 형제 요소로 추가
- 요소.remove(): 선택된 요소 삭제
- 부모요소.removeChild('부모요소의 자식요소'): 선택된 요소의 자식 요소 삭제
예제
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM 요소 추가 & 삭제</title>
<!--js파일 분리해서 사용하는 경우, defer 추가해서 dom요소를 로드 후에 실행!-->
<script defer>
let ul = document.querySelector('ul');
console.log(ul); // null
// 스크립트는 위에서부터 아래로 읽히기 때문에, 아직 ul 태그가 만들어지지 않아 null 값이 나오게 된다
// script를 바디 안에 작성하던가
// script 속성으로 defer을 주던가(body를 읽고 나서,,)
// 함수 안에 작성하던가
function createNode(){
//let ul = document.querySelector('ul');
//console.log(ul);
let li = document.createElement('li');
li.innerText = '새로 추가된 리스트';
console.log(li);
}
function appendNode(){
let li = document.createElement('li');
li.innerText = '새로 추가된 리스트';
// append(): 요소.append() - 선택한 요소의 맨 뒤 자식 요소로 추가
let ul = document.querySelector('ul');
ul.append(li);
ul.append('text');
}
function appendChildNode(){
let li = document.createElement('li');
li.innerText = '새로 추가된 리스트 append child';
// appendChild(): 요소.appendChild() - 선택한 요소의 맨 뒤 자식 요소로 추가
let ul = document.querySelector('ul');
ul.appendChild(li);
ul.appendChild("text"); // 에러발생 -> appendChild에는 위의 child와 다르게 문자열이 들어가지 못하고 요소 노드만 추가 가능
}
function prependNode(){
let li = document.createElement('li');
li.innerText = '새로 추가된 리스트 prepend child';
// prepend(): 요소.prepend() - 선택한 요소의 맨 앞 자식 요소로 추가
let ul = document.querySelector('ul');
ul.prepend(li);
}
function before(){
// 기존 노드 앞에 형제 요소로 추가
let standard = document.getElementById("li");
let li = document.createElement("li");
li.innerText = 'before';
standard.before(li);
}
function after(){
// 기존 노드 뒤에 형제 요소로 추가
let standard = document.getElementById("li");
let li = document.createElement("li");
li.innerText = 'after';
standard.after(li);
}
function removeNode(){
// 내가 선택한 요소 삭제
let standard = document.getElementById("li");
// 삭제할 요소에 remove()
standard.remove();
}
function removeChildNode(){
let ul = document.querySelector('ul');
let standard = document.getElementById("li");
// 삭제할 요소의 부모에 removeChild()
console.log(ul.removeChild(standard));
}
</script>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li id="li">3</li>
<li>4</li>
<li>5</li>
</ul>
<button onclick="createNode()">createNode</button>
<button onclick="appendNode()">appendNode</button>
<button onclick="appendChildNode()">appendChildNode</button>
<button onclick="prependNode()">prependNode</button>
<button onclick="before()">before</button>
<button onclick="after()">after</button>
<button onclick="removeNode()">removeNode</button>
<button onclick="removeChildNode()">removeChildNode</button>
</body>
</html>