2-2. DOM 제어와 조작
자식 노드탐색
- firstChild, lastChild 는 사용 X (띄어쓰기 같은 것들까지 모두 자식으로 싸잡기 때문에 문제가 많음)
- firstElementChild, lastElementChild로 사용
부모형제 노드탐색
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div class="wrap">
<ul id="fruits">
<li class="fr apple">사과</li>
<li class="fr banana">바나나</li>
<li class="fr grape">포도</li>
</ul>
</div>
<div id="wrapper">
<section class="contents">
<ul class="list">
<li class="item"><a href="#" class="link">10</a></li>
<li class="item"><a href="#" class="link active">20</a></li>
<li class="item"><a href="#" class="link">30</a></li>
</ul>
<ul class="list">
<li class="item"><a href="#0." class="link">40</a></li>
<li class="item"><a href="#" class="link">50</a></li>
<li class="item"><a href="#" class="link">60</a></li>
</ul>
</section>
</div>
- 위와 같은 html이 있고, active 클래스에서 60이라는 숫자가 있는 곳으로 가려고한다면 아래처럼 굉장히 복잡하게 가야만 한다.
const $active = document.querySelector('.active');
console.log($active
.parentElement
.parentElement
.nextElementSibling
.lastElementChild
.firstElementChild);
- 그래서 이러한 불편함을 해결하기위해 기준태그로부터
위로 올라가면서 탐색
하는 함수가 존재!
->closest('css selector');
- 아래방향 탐색은 querySelector를 이용!
- 그래서 두 함수를 이용해서 위와 같은 60이 있는 곳을 구한다면? 아래와 같이 구할 수 있다.
const $contentSect = $active.closest('section.contents');
const $found = $contentSect
.querySelector('ul.list:nth-child(2)')
.querySelector('li.item:last-child');
텍스트, html 조작
- 요소 노드의 textContent 프로퍼티를 참조하면 요소 내부의 모든
텍스트를 반환
-> 이 때 마크업은 무시됨
- 과거에 사용하던
innerText
는 유사한 원리로 작동하나 속도가 느리고 css에 의해 숨겨진 텍스트 (ex - 내부 태그에 작성된 텍스트)를 반환하지 않으니 사용 X
- innerHTML 프로퍼티는 요소 노드 내부의 HTML 마크업을 취득하거나 변경
- textContent는 HTML마크업을 무시하고 텍스트만 반환하지만 innerHTML은
마크업이 포함된 문자열을 그대로 반환
li<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.highlight {
color: red;
}
</style>
</head>
<body>
<div id="greet">
Hello <span class="highlight">World</span>
</div>
<ul id="food-list">
<li class="food">짜장면</li>
<li class="food">파스타</li>
<li class="food">볶음밥</li>
</ul>
<script>
const $greet = document.getElementById('greet');
console.log($greet.textContent.trim);
$greet.querySelector('.highlight').textContent = '자바스크립트!';
const $foodList = document.getElementById('food-list');
$foodList.innerHTML += '<li class="food">볶음밥</li>'
$foodList.innerHTML = ['']
const foods = ['연어회', '고등어찜', '오렌지', '딸기'];
foods.forEach(f =>
$foodList.innerHTML += `
<li class="food">${f}</li>
`)
</script>
</body>
</html>
$greet.textContent = '안녕 자바스크립트!';
위 html에서 이런식으로 작성하게된다면 내부에 있는 텍스트들까지 싸잡혀서 한꺼번에 수정되어 사라질 수 있음
-> $greet.querySelector('.highlight').textContent = '자바스크립트!';
와 같이 겹치지않게 최대한 자세하게 작성해주면 해결할 수 있음
- 또한 innerHTML을 통해 자식 태그를 추가, 삭제 할 수 있음
-> textContent가 안되는 이유는 마크업이 무시되고 텍스트만 반환되기 때문.
태그 생성 및 추가
- 요소 노드 생성
- createElement(tagName) 메서드는 요소 노드를 새롭게 생성하여 반환
- 매개변수 tagName에는 태그 이름을 나타내는 문자열을 인수로 전달
- 이 메서드로 생성된 요소 노드는 아무런 자식 노드도 가지지 않음. 즉, 텍스트 노드도 없는 상태 -> textContent를 이용해 텍스트 추가!
- 해당 메서드는 요소 노드를 생성할 뿐 DOM에 추가하지 않으므로 DOM에 추가하는 사후 처리가 필요 !!!
- appendChild(childNode) 메서드는 매개변수에게 전달한 자식 노드를 호출한 부모노드의
마지막 자식으로
추가
- 자식 노드로 텍스트 노드를 전달하면 텍스트가 추가
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul id="fruits">
<li>사과</li>
</ul>
<script>
function makeNewTag(tagName='div', options={}) {
const $newTag = document.createElement(tagName);
if (options.text) $newTag.textContent = options.text;
return $newTag;
}
function append($parent, tagName, options) {
$parent.appendChild(makeNewTag(tagName, options));
}
const $ul = document.getElementById('fruits');
append($ul, 'li', { text: '바나나' });
append($ul, 'li', { text: '망고' });
append($ul, 'li', { text: '오렌지' });
</script>
</body>
</html>
- 태그 생성 (createElement)
- 텍스트 추가 (textContent)
- 자식노드로 붙이기 (appendChild)
순서로 진행하면 태그 이름과 텍스트를 전달받으며 태그를 생성할 수 있다!
-> 이처럼 한개씩 하는 것도 좋지만 함수를 만들어 한 번에 처리하는 것에 익숙해지면 더 편리해질 수 있음
태그 중간 삽입
- insertBefore(newNode, childNode) 메서드는 첫 번째 인수로 전달받은 노드를 두 번째 인수로 전달받은 노드
앞에 삽입
- 두 번째 인수로 전달받은 노드는 반드시 insertBefore를 호출한 노드의 자식이어야 합니다. 그렇지 않으면 DOMException 에러가 발생
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul id="fruits">
<li>사과</li>
<li>바나나</li>
<li>포도</li>
</ul>
<script>
const $fruits = document.getElementById('fruits');
const [$apple, $banana, $grape] = [...$fruits.children];
const $newLi = document.createElement('li');
$newLi.textContent = '망고';
$fruits.insertBefore($newLi, document.querySelector('li:nth-child(2)'));
$fruits.appendChild($apple);
$fruits.insertBefore($banana, null);
</script>
</body>
</html>
- appendChild를 이용해 추가하면 맨 뒤에 추가되는 것이므로 노드를 이동 시킬 수 있음
- insertBefore에서 두 번째 인수 (위치)가 null이면 맨 뒤로 감
- 위 예시에서 보다시피appendChild, insertBefore를 이용해서 노드 이동 가능~!
태그 교체, 삭제
- replaceChild() 메서드는 자신을 호출한 노드의 자식 노드를 다른 노드로 교체
- 없애버릴 자식노드를 oldChild에 인수로 전달, 그 자리를 새롭게 차지할 자식노드를 newChild에 전달
- removeChild() 메서드는 child 매개 변수에 인수로 전달한 노드를 DOM에서 제거
- 전달된 노드는 반드시 호출한 노드의 자식노드이어야 함
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul id="fruits">
<li>사과</li>
<li>바나나</li>
<li>포도</li>
</ul>
<script>
const $fruits = document.getElementById('fruits');
const [$apple, $banana, $grape] = [...$fruits.children];
const $newDiv = document.createElement('div');
$newDiv.textContent = '빡스';
$fruits.replaceChild($newDiv, $banana);
$fruits.removeChild($apple);
const children = [...$fruits.children];
for (let i = 0; i < children.length; i++) {
$fruits.removeChild(children[i]);
}
</script>
</body>
</html>
- 자식노드 전체삭제하는 것이 없다면? 만들자!
-> 유사배열을 [...] 을 통해 배열로 만든 후에 forEach를 이용해서 removeChild를 반복문 돌리면 됨