노드 탐색
그림
- DOM 트리 상의 노드를 탐색할 수 있도록 Node, Element 인터페이스는 트리 탐색 프로퍼티 제공
- 노드 탐색 프로퍼티는 모두 접근자 프로퍼티로 getter 만 존재하여 참조만 가능한 읽기 전용 접근자 프로퍼티
1. 공백 텍스트 노드
- HTML 요소 사이의 스페이스, 탭, 줄바꿈 등의 공백 문자의 텍스트 노드
- 노드를 탐색할 때 공백 문자가 생성한 공백 텍스트 노드에 주의 해야함
2. 자식 노드 탐색
| 프로퍼티 | 설명 |
| Node.prototype.childNodes | - 자식 노드를 모두 탐색하여 DOM 컬렉션 객체인 NodeList 에 담아 반환 - 텍스트 노드 또는 요소 노드 반환 |
| Element.prototype.children | - 자식 노드 중에서 요소 노드만 모두 탐색하여 DOM 컬렉션 객체인 HTMLCollection 에 담아 반환 - 텍스트 노드 미포함 |
| Node.prototype.firstChild | - 첫 번째 자식 노드 반환 - 텍스트 노드 또는 요소 노드 반환 |
| Node.prototype.lastChild | - 마지막 자식 노드 반환 - 텍스트 노드 또는 요소 노드 반환 |
| Element.prototype.firstElementChild | - 첫 번째 자식 요소 노드 반환 - 요소 노드만 반환 |
| Element.prototype.lastElementChild | - 마지막 자식 요소 노드 반환 - 요소 노드만 반환 |
3. 자식 노드 존재 확인
- 자식 노드가 존재하는지 확인하려면 Node.prototype.hasChildNodes 메서드 사용
- 자식 노드 존재 여부에 따라 불리언 값 반환
- childNodes 프로퍼티와 마찬가지로 텍스트 노드를 포함하여 자식 노드 존재 확인
- 자식 노드 중에 텍스트 노드가 아닌 요소 노드가 존재하는지 확인하려면 children.length 또는 childElementCount 프로퍼티 사용
4. 요소 노드의 텍스트 노드 탐색
- 요소 노드의 텍스트 노드는 요소 노드의 자식 노드로 firstChild 프로퍼티로 접근 가능
5. 부모 노드 탐색
- 부모 노드를 탐색하려면 Node.prototype.parentNode 프로퍼티를 사용
- 텍스트 노드는 DOM 트리의 최종단 노드인 리프 노드로 부모 노드가 텍스트 노드인 경우는 X
6. 형재 노드 탐색
- 어트리뷰트 노드는 요소 노드와 연결되어 있지만 부모 노드가 같은 형제 노드가 아니기 때문에 반환 X
| 프로퍼티 | 설명 |
| Node.prototype.previousSibling | - 부모 노드가 같은 형제 노드 중에서 자신의 이전 형제 노드를 탐색하여 반환 - 요소 노드 또는 텍스트 노드 반환 |
| Node.prototype.nextSibiling | - 부모 노드가 같은 형제 노드 중에서 자신의 다음 형제 노드를 탐색하여 반환 - 요소 노드 또는 텍스트 노드 반환 |
| Node.prototype.previousElementSibling | - 부모 노드가 같은 형제 요소 노드 중에서 자신의 이전 형제 요소 노드를 탐색하여 반환 - 요소 노드 반환 |
| Node.prototype.nextElementSibling | - 부모 노드가 같은 형제 요소 노드 중에서 자신의 다음 형제 요소 노드를 탐색하여 반환 - 요소 노드 반환 |
노드 정보 취득
| 프로퍼티 | 설명 |
| Node.prototype.nodeType | 노드 객체의 종류, 즉 노드 타입을 나타내는 상수 반환 또는 노드 타입 상수는 Node에 정의 되어 있음 - Node.ELEMENT_NODE : 요소 노드 타입을 나타내는 상수 1 반환 - Node.TEXT_NODE : 텍스트 노드 타입을 나타내느 상수 3 반환 - Node.DOCUMENT_NODE : 문서 노드 타입을 나타내는 상수 9 반환 |
| Node.prototype.nodeName | 노드의 이름을 문자열로 반환 - 요소 노드 : 대문자 문자열로 태그 이름 'UL', 'LI' 등을 반환 - 텍스트 노드 : 문자열 #text 반환 - 문서 노드 : 문자열 #document 반환 |
<!DOCTYPE html>
<html>
<body>
<div id="foo">Hello</div>
</body>
<script>
console.log(document.nodeType); // 9
console.log(document.nodeName); // '#document'
const $foo = document.getElementById('foo');
console.log($foo.nodeType) // 1
console.log($foo.nodeName) // DIV
const $textNode = $foo.firstChild;
console.log($textNode.nodeType) // 3
console.log($textNode.nodeName) // '#text'
</script>
</html>
요소 노드의 텍스트 조작
1. nodeValue
- 노드 객체의 nodeValue 프로퍼티를 참조하면 노드 객체의 값 (텍스트 노드의 텍스트) 반환
<!DOCTYPE html>
<html>
<body>
<div id="foo">Hello</div>
</body>
<script>
// 문서 노드의 nodeValue 프로퍼티를 참조
console.log(document.nodeValue); // null
// 요소 노드의 nodeValue 프로퍼티를 참조
const $foo = document.getElementById('foo');
console.log($foo.nodeValue) // null
// 텍스트 노드의 nodeValue 프로퍼티 참조
const $textNode = $foo.firstChild;
console.log($textNode.nodeValue) // 'Hello'
</script>
</html>
1-1. nodeValue 텍스트 변경
- 텍스트를 변경할 요소 노드 취득 → 취득한 요소 노드의 텍스트 노드 탐색
- 탐색한 노드의 nodeValue 프로퍼티를 사용하여 텍스트 노드 값 변경
<!DOCTYPE html>
<html>
<body>
<div id="foo">Hello</div>
</body>
<script>
// 1. #foo 요소 노드의 자식 노드인 텍스트 노드 취득
const $textNode = document.getElementById('foo').firstChild
// 2. nodeValue 프로퍼티를 사용하여 텍스트 노드의 값 변경
$textNode.nodeValue = 'World'
</script>
</html>
2. textContent
- 요소노드의 textContent 프로퍼티를 참조하면 요소 노드의 콘텐츠 영역 (시작 태그와 종료 태그 사이) 내의 텍스트 모두 반환
- 요소노드의 childeNodes 프로퍼티가 반환한 모든 노드들의 텍스트를 모드 반환 (HTML 마크업은 무시됨)
<!DOCTYPE html>
<html>
<body>
<div id="foo">Hello <span>World!</span></div>
</body>
<script>
// 요소 노드의 텍스트를 모두 취득 (HTML 마크업 무시)
console.log(document.getElementById('foo').textContent)
</script>
</html>
2-1. textContent 텍스트 변경
<!DOCTYPE html>
<html>
<body>
<div id="foo">Hello <span>World!</span></div>
</body>
<script>
// 요소 노드의 모든 자식 노드가 제거되고 할당한 문자열이 텍스트로 추가 (HTML 마크업 파싱X)
document.getElementById('foo').textContet = 'Hi <span>Echichi!</span>'
// 'Hi <span>Echichi!</span>'
</script>
</html>
textContent 프로퍼티와 유사한 동작을 하는 innerText가 있지만 사용하지 않는 것이 좋다
- innerText 프로퍼티는 CSS 에 순종적으로 CSS 에 의해 비표시로 지정된 요소 노드의 텍스트를 반환 X
- innerText 프로퍼티는 CSS 를 고려해야 하므로 textContent 프로퍼티보다 느림
DOM 조작
- DOM 조작에 의해 새로운 노드가 추가되거나 삭제되면 리플로우와 리페인트가 발생하는 원인이 되므로 성능에 악영향
- 성능 최적화를 위해 주의해서 다루어야 함
1. innerHTML
- Element.prototype.innerHTML 프로퍼티는 setter와 getter 가 모두 존재하는 접근자 프로퍼티
- 요소 노드의 HTML 마크업을 취득하거나 변경
- 요소 노드의 콘텐츠 영역 내에 포함된 모든 HTML 마크업을 문자열로 변환
- textContent 프로퍼티는 HTML 마크업을 무시하고 텍스트만 반환하지만 innerHTML 프로퍼티는 HTML 마크업이 포함된 문자열 그래로 반환
- 요소 노드의 모든 자식 노드가 제거되고 할당한 문자열에 포함되어 있는 HTML 마크업이 파싱되어 요소 노드의 자식 노드로 DOM에 반영
<!DOCTYPE html>
<html>
<body>
<div id="foo">Hello <span>World!</span></div>
</body>
<script>
// HTML 마크업이 파싱되어 요소 노드의 자식 노드로 DOM에 반영
document.getElementById('foo').innerHTML = 'Hi <span>Echichi!</span>'
</script>
</html>
- innerHTML 프로퍼티에 할당한 HTML 마크업 문자열은 렌더링 엔진에 의해 파싱되어 요소 노드의 자식으로 DOM에 반영되기 때문에 크로스 사이트 스크립트 공격 (XSS) 에 취약
- HTML 새니티제이션 : 사용자로부터 입력받은 데이터에 의해 발생할 수 있는 크로스 사이트 스크립팅 공격을 예방하기 위해 잠재적 위험을 제거하는 기능
- 복잡하지 않은 요소를 새롭게 추가할 때 유용하지만 기존 요소를 제거하지 않으면서 위치를 지정해 새로운 요소를 삽입해야 할 때는 사용하지 않는 것이 좋음
2. insertAdjacnetHTML 메서드
- Element.prototype.insertAdjacentHTML 메서드는 기존 요소를 제거하지 않으면서 위치를 지정해 새로운 요소를 삽입
- 두 번째 인수로 전달한 HTML 마크업 문자열을 파싱하고 그 결과로 생성된 노드를 첫 번째 인수로 전달한 위치에 삽입하여 DOM에 반영
- 기존 요소에는 영향을 주지 않고 새롭게 삽입될 요소만을 파싱하여 자식 요소로 추가하므로 innerHTML 프로퍼티보다 효율적이고 빠름
- HTML 마크업 문자열을 파싱하므로 크로스 사이트 스크립팅 공격에 취약
<!DOCTYPE html>
<html>
<body>
<!-- beforebegin -->
<div id="foo">
<!-- afterbegin -->
Hello
<!-- beforeend -->
</div>
<!-- afterend -->
</body>
<script>
const $foo = document.getElementById('foo');
$foo.insertAdjacentHTML('beforebegin','<p>beforebegin</p>');
$foo.insertAdjacentHTML('afterbegin','<p>afterbegin</p>');
$foo.insertAdjacentHTML('beforeend','<p>beforeend</p>');
$foo.insertAdjacentHTML('afterend','<p>afterend</p>');
</script>
</html>
3. 노드 생성과 추가
- DOM 노드를 직접 생성/ 삽입/ 삭제/ 치환하는 메서드 제공
3-1. 요소 노드 생성
- Document.prototype.createElement(tagName) 메서드는 요소 노드를 생성하여 반환
- 요소 노드를 생성할 뿐 DOM 에 추가하지 않으므로 추가 처리가 별도 필요
// 요소 생성
const $li = document.createElement('li');
3-2. 텍스트 노드 생성
- Document.prototype.createTextNode(text) 메서드는 텍스트 노드를 생성하여 반환
- 텍스트 노드를 생성할 뿐 요소 노드에 추가하지 않으므로 추가 처리 필요
// 텍스트 노드 생성
const textNode = document.createTextNode('Banana');
3-3. 텍스트 노드를 요소 노드의 자식 노드로 추가
- Node.prototype.appendChild(childNode) 메서드는 매개변수 childNode 에게 인수로 전달한 노드를 appendChild 메서드를 호출한 노드의 마지막 자식 노드로 추가
- 요소 노드에 자식 노드가 하나도 없는 경우 textContent 프로퍼티를 사용하는 편이 더욱 간편
// = $li.appendChild(textNode);
$li.textContent = "Banana;";
3-4. 요소 노드를 DOM에 추가
- Node.prototype.appendChild(childNode) 메서드를 사용하여 텍스트 노드와 부자 관계로 연결한 요소 노드를 #fruits 요소 노드의 마지막 자식 요소로 추가
// $li 요소 노드를 #fruits 요소 노드의 마지막 자식 노드로 추가
$fruits.appendChild($li);
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
</ul>
</body>
<script>
const $fruits = document.getElementById("fruits");
// 요소 생성
const $li = document.createElement("li");
// 텍스트 노드 생성
const textNode = document.createTextNode("Banana");
// = $li.appendChild(textNode);
$li.textContent = "Banana";
// $li 요소 노드를 #fruits 요소 노드의 마지막 자식 노드로 추가
$fruits.appendChild($li);
</script>
</html>
4. 복수의 노드 생성과 추가
- DocumentFragment 노드는 문서, 요소, 어트리뷰트, 텍스트 노드와 같은 노드 객체의 일종으로 부모 노드가 없어서 기존 DOM 과는 별도로 존재한다는 특장
- 자식 노드들의 부모 노드로서 별도의 서브 DOM 을 구성하여 기존의 DOM에 추가하기 위한 용도로 사용
- Document.prototype.createDocumentFragment 메서드는 비어있는 DocumentFragment 노드를 생성하여 반환
<!DOCTYPE html>
<html>
<body>
<ul id="fruits"></ul>
</body>
<script>
const $fruits = document.getElementById("fruits");
// Documentfragment 노드 생성
const fragment = document.createDocumentFragment();
["Apple", "Banana", "Orange"].forEach((text) => {
// 1. 요소 노드 생성
const $li = document.createElement("li");
// 2. 택스트 추가
$li.textContent = text;
// 3. 요소 노드를 Documentfragment노드에 추가
fragment.appendChild($li);
});
// Documentfragment 노드를 $fruits 요소 노드의 마지막 자식 노드로 추가
$fruits.appendChild(fragment);
</script>
</html>
5. 노드 삽입
5-1. 마지막 노드로 추가
- Node.prototype.appendChild 메서드는 인수로 전달받은 노들르 자신을 호출한 노드의 마지막 자식 노드로 DOM 에 추가
5-2. 지정한 위치에 삽입
- Node.prototype.insertBefore(newNode, childNode) 메서드는 첫 번째 인수로 전달받은 노드를 두 번째 인수로 전달 받은 노드 앞에 삽입
- 두 번째 인수로 전달받은 노드는 반드시 insertBefore를 호출한 노드의 자식 노드여야함 (DOMException 발생)
- 두 번째 인수로 전달받은 노드가 null 이면 마지막 자식 노드로 추가됨 (=appendChild)
<!DOCTYPE html>
<html>
<body>
<div>test</div>
<ul id="fruits"></ul>
</body>
<script>
const $fruits = document.getElementById("fruits");
const fragment = document.createDocumentFragment();
["Apple", "Banana", "Orange"].forEach((text) => {
const $li = document.createElement("li");
$li.textContent = text;
fragment.appendChild($li);
});
$fruits.appendChild(fragment);
const $li = document.createElement("li");
$li.textContent = "appendChild";
$fruits.appendChild($li);
const $li2 = document.createElement("li");
$li2.textContent = "insertBefore";
$fruits.insertBefore($li2, $fruits.firstChild);
</script>
</html>
6. 노드 이동
- DOM 에 이미 존재하는 노드를 appendChild 또는 insertBefore 메서드를 사용하여 DOM에 다시 추가하면 현재 위치에서의 Node 를 제거하고 새로운 위치에 노드를 추가하여 노드가 이동됨
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
<li>Orange</li>
</ul>
</body>
<script>
const $fruits = document.getElementById("fruits");
// 이미 존재하는 요소 노드 취득
const [$apple, $banana] = $fruits.children;
// 이미 존재하는 $apple 요소 노드를 $fruits 요소 노드의 마지막 노드로 이동
$fruits.appendChild($apple); // Banana - Orange - Apple
// 이미 존재하는 $banana 요소 노드를 #fruits 요소의 마지막 자식 노드 앞으로 이동
$fruits.insertBefore($banana, $fruits.lastElementChild); // Orange - Banana - Apple
</script>
</html>
7. 노드 복사
- Node.prototype.cloneNode([deep : true | false]) 메서드는 노드의 사본을 생서앟여 반환
- 매개변수 deep 에 true 를 인수로 전달하면 노드를 깊은 복사하여 모든 자손 노드가 포함된 사본을 생성, false 를 인수로 전달하거나 생략하면 노드를 얕은 복사하여 노드 자신만의 사본 생성
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
</ul>
</body>
<script>
const $fruits = document.getElementById("fruits");
const $apple = $fruits.firstElementChild;
// $apple 요소를 얕은 복사하여 사본 생성 (텍스트 노드가 없는 사본 생성)
const $shallowClone = $apple.cloneNode();
// 사본 요소 노드에 텍스트 추가
$shallowClone.textContent = "Banana";
// 사본 요소 노드를 #fruits 요소 노드 마지막 노드로 추가
$fruits.appendChild($shallowClone);
// #fruits 요소를 깊은 복사하여 모든 자손 노드가 포함된 사본 생성
const $deepClone = $fruits.cloneNode(true);
$fruits.appendChild($deepClone);
</script>
</html>
8. 노드 교체
- Node.prototype.replaceChild(newChild, oldChild) 메서드는 자신을 호출한 노드의 자식 노드를 다른 노드로 교체
- oldChild 매개변수에 인수로 전달한 노드는 replaceChild 메서드를 호출한 노드의 자식 노드여야함
- 자신을 호출한 노드의 자식 노드인 oldChild 노드를 newChild 노드로 교체 (oldChild 노드는 DOM에서 제거)
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
</ul>
</body>
<script>
const $fruits = document.getElementById("fruits");
const $apple = $fruits.firstElementChild;
// 기존 노드와 교체할 요소 노드 생성
const $newChild = document.createElement("li");
$newChild.textContent = "Banana";
// #fruits 요소 노드의 첫 번째 자식 요소 노드를 $newChild 요소 노드로 교체
$fruits.replaceChild($newChild, $apple);
</script>
</html>
9. 노드 삭제
- Node.prototype.removeChild(child) 메서드는 child 매개변수에 인수로 전달한 노드를 DOM에서 삭제
- 인수로 전달한 노드는 removeChild 메서드를 호출한 노드의 자식 노드이어야 함
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
</ul>
</body>
<script>
const $fruits = document.getElementById("fruits");
// #fruits 요소 노드의 마지막 요소를 DOM에서 삭제
$fruits.removeChild($fruits.lastElementChild);
</script>
</html>
'Javascript' 카테고리의 다른 글
| [모던 자바스크립트 Deep Dive] 37. 이벤트 (2) | 2023.12.06 |
|---|---|
| [모던 자바스크립트 Deep Dive] 36-3. DOM (1) | 2023.12.03 |
| [모던 자바스크립트 Deep Dive] 36-1. DOM (1) | 2023.12.01 |
| [모던 자바스크립트 Deep Dive] 35. 브라우저의 렌더링 과정 (1) | 2023.11.29 |
| [모던 자바스크립트 Deep Dive] 34. Set 과 Map (1) | 2023.11.28 |