브라우저의 렌더링 엔진은 HTML 문서를 파싱하여 브라우저가 이해할 수 있는 자료구조인 DOM을 생성한다.
DOM은 HTML 문서의 계층적 구조와 정보를 표현하며 이를 제어할 수 있는 API, 즉 프로퍼티와 메서드를 제공하는 트리 구조이다.
노드
HTML 요소와 노드 객체
HTML 문서는 HTML 문서를 구성하는 개별적인 요소를 의미한다.
HTML 요소는 렌더링 엔진에 의해 파싱되어 DOM을 구성하는 요소 노드 객체로 변환된다.
이때 어트리뷰트는 어트리뷰트 노드로, 텍스트 콘텐츠는 텍스트 노드로 변환된다.
HTML 요소 간에 중첩 관계에 의한 계층적인 부자 관계가 형성되며 모든 노드 객체들을 트리 자료구조로 구성한다.
트리 자료구조
트리 자료구조는 노드들의 계층 구조로 이뤄지며 부모 노드와 자식 노드로 구성된 계층적 구조의 비선형 자료구조이다.
노드 객체의 타입
아래의 HTML 문서를 렌더링 엔진이 파싱한다고 생각해보자.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<ul>
<li id="apple">Apple</li>
<li id="banana">Banana</li>
<li id="orange">Orange</li>
</ul>
<script src="app.js"></script>
</body>
</html>
렌더링 엔진은 위의 HTML 문서를 파싱하여 다음과 같이 DOM을 생성한다.
DOM은 노드 객체의 계층적인 구조로 구성된다.
문서 노드
DOM 트리의 최상위에 존재하는 루트 노드로서 document 객체를 가리키며 전역객체 window의 document 프로퍼티에 바인딩되어 있다.
브라우저 환경의 모든 JS 코드는 script 태그에 의해 분리되어 있어도 하나의 window를 공유한다. 즉 HTML 문서당 하나의 document 객체가 있다.
document 객체는 DOM 트리의 노드들에 접근하기 위한 진입점 역할을 담당한다.
요소 노드
요소 노드는 HTML 요소를 가리키는 객체이며 문서의 관계 구조를 표현한다고 할 수 있다.
어트리뷰트 노드
HTML 요소의 어프리뷰트를 가리키는 객체이며 어트리뷰트가 지정된 HTML 요소의 요소 노드와 연결되어 있다. 요소 노드는 부모 노드와 연결되어 있지만, 어트리뷰트 노드는 부모 노드와 연결되어 있지 않고 요소 노드에만 연결되어 있다. 따라서 어트리뷰트 노드에 접근하기 위해서는 요소 노드에 먼저 접근해야 한다.
텍스트 노드
HTML 요소의 텍스트를 가리키는 객체이며 문서의 정보를 표현하며 DOM 트리의 최조단이다.
노드 객체의 상속 구조
DOM은 HTML 문서의 계층적 구조와 정보를 표현하며 이를 제어할 수 있는 API, 즉 프로퍼티와 메서드를 제공하는 트리 자료구조이다. 이를 통해 DOM을 구성하는 노드 객체는 자신의 구조와 정보를 제어할 수 있는 DOM API를 사용할 수 있다.
DOM을 구성하는 노드 객체는 ECMAScript 사양에 정의된 표준 빌트인 객체가 아닌 브라우저 환경에서 추가적으로 제공하는 호스트 객체이며 노드 객체도 JS 객체이므로 프로토타입에 의한 상속 구조를 갖는다.
모든 노드 객체는 Object, EventTarget, Node 인터페이스를 상속받으며 문서 노드는 Document, HTMLDocument 인터페이스를 상속받고 어트리뷰트 노드는 Attr, 텍스트 노드는 CharacterData 인터페이스를 각각 상속 받는다. 요소 노드는 Element 인터페이스와 HTMLElement와 태그별 세분화된 인터페이스(HTMLBodyElement 등)를 상속받는다.
예를 들어 input요소를 파싱해 객체호한 input 요소 노드 객체는 HTMLInputElement, HTMLElement 등의 인터페이스를 상속받는다.
<!DOCTYPE html>
<html>
<body>
<input type="text">
<script>
// input 요소 노드 객체를 선택
const $input = document.querySelector('input');
// input 요소 노드 객체의 프로토타입 체인
console.log(
Object.getPrototypeOf($input) === HTMLInputElement.prototype,
Object.getPrototypeOf(HTMLInputElement.prototype) === HTMLElement.prototype,
Object.getPrototypeOf(HTMLElement.prototype) === Element.prototype,
Object.getPrototypeOf(Element.prototype) === Node.prototype,
Object.getPrototypeOf(Node.prototype) === EventTarget.prototype,
Object.getPrototypeOf(EventTarget.prototype) === Object.prototype
); // 모두 true
</script>
</body>
</html>
input 요소 노드 객체의 특성 | 프로토타입을 제공하는 객체 |
객체 | Object |
이벤트를 발생시키는 객체 | EventTarget |
트리 자료구조의 노드 객체 | Node |
브라우저가 렌더링할 수 있는 웹 문서의 요소(HTML, XML, SVG)를 표현하는 객체 | Element |
웹 문서의 요소 중에서 HTML 요소를 표현하는 객체 | HTMLElement |
HTML 요소 중에서 input 요소를 표현하는 객체 | HTMLInputElement |
노드 객체의 상속 구조는 개발자 도구의 Elements-Properties 패널에서 확인할 수 있다.
노드 객체에는 노드 객체의 종류에 따라 공통으로 갖는 기능도 있으며, 고유한 기능도 있다.
예를 들어, 모든 노드 객체는 input 요소 노드 객체와 div 요소 노드객체는 모두 HTML 요소의 스타일을 나타내는 style 프로퍼티가 있으며, 이처럼 HTML 요소가 공통적으로 갖는 기능은 HTMLElement 인터페이스가 제공한다.
하지만 input의 value 프로퍼티가 있지만 div에는 없듯이 고유한 프로퍼티가 있는데, 이러한 경우 HTMLInputElement, HTMLDivElement 등 HTML 요소 종류에 따라 각각 다르다.
정리하자면 DOM은 HTML 문서의 계층적 구조와 정보를 표현하는 것은 물론 노트 타입에 따라 필요한 기능을 프로퍼티와 메서드의 집한인 DOM API로 제공한다.
하지만 중요한 것은 DOM이 제공하는 프로퍼티와 메서드인 DOM API를 이용해 노드에 접근하고 HTML의 구조나 내용 또는 스타일 등을 동적으로 변경하는 방법을 익히는 것이다.
프론트엔드 개발자에게 HTML은 단순히 태그와 어트리뷰트를 선언적으로 배치해 뷰를 구성하는 것 이상으로 DOM과 연관지어 바라볼 줄 알아야 한다.
요소 노드 취득
HTML의 구조나 내용 또는 스타일 등을 동적으로 조작하려면 먼저 요소 노드를 취득해야 한다. 이를 위해 DOM은 요소 노드를 취득할 수 있는 다양한 메서드를 제공한다.
id를 이용한 요소 노드 취득
Document.prototype.getElementById 메서드는 인수로 전달한 id를 갖는 하나의 요소 노드를 탐색해 반환한다.
<!DOCTYPE html>
<html>
<body>
<ul>
<li id="apple">Apple</li>
<li id="banana">Banana</li>
<li id="orange">Orange</li>
</ul>
<script>
// id 값이 'banana'인 요소 노드를 탐색하여 반환한다.
// 두 번째 li 요소가 파싱되어 생성된 요소 노드가 반환된다.
const $elem = document.getElementById('banana');
// 취득한 요소 노드의 style.color 프로퍼티 값을 변경한다.
$elem.style.color = 'red';
</script>
</body>
</html>
중복된 id 값이 있는 경우 첫 번째 요소 노드만 반환한다.
<!DOCTYPE html>
<html>
<body>
<ul>
<li id="banana">Apple</li>
<li id="banana">Banana</li>
<li id="banana">Orange</li>
</ul>
<script>
// getElementById 메서드는 언제나 단 하나의 요소 노드를 반환한다.
// 첫 번째 li 요소가 파싱되어 생성된 요소 노드가 반환된다.
const $elem = document.getElementById('banana');
// 취득한 요소 노드의 style.color 프로퍼티 값을 변경한다.
$elem.style.color = 'red';
</script>
</body>
</html>
전달한 id 값을 갖는 HTML 요소 노드가 없다면 null을 반환한다.
<!DOCTYPE html>
<html>
<body>
<ul>
<li id="apple">Apple</li>
<li id="banana">Banana</li>
<li id="orange">Orange</li>
</ul>
<script>
// id 값이 'grape'인 요소 노드를 탐색하여 반환한다. null이 반환된다.
const $elem = document.getElementById('grape');
// 취득한 요소 노드의 style.color 프로퍼티 값을 변경한다.
$elem.style.color = 'red';
// -> TypeError: Cannot read property 'style' of null
</script>
</body>
</html>
HTML 요소에 id 어트리뷰트를 부여하면 id 값과 동일한 이름의 전역 변수가 암묵적으로 선언되고 해당 노드 객체가 할당되는 부수 효과가 있다.
<!DOCTYPE html>
<html>
<body>
<div id="foo"></div>
<script>
// id 값과 동일한 이름의 전역 변수가 암묵적으로 선언되고 해당 노드 객체가 할당된다.
console.log(foo === document.getElementById('foo')); // true
// 암묵적 전역으로 생성된 전역 프로퍼티는 삭제되지만 전역 변수는 삭제되지 않는다.
delete foo;
console.log(foo); // <div id="foo"></div>
</script>
</body>
</html>
id 값과 동일한 이름의 전역 변수가 이미 선언되어 있으면 해당 전역 변수에 노드 객체가 재할당되지 않는다.
<!DOCTYPE html>
<html>
<body>
<div id="foo"></div>
<script>
let foo = 1;
// id 값과 동일한 이름의 전역 변수가 이미 선언되어 있으면 노드 객체가 재할당되지 않는다.
console.log(foo); // 1
</script>
</body>
</html>
태그 이름을 이용한 요소 노드 취득
Document.prototype/Element.prototype.getElementsByTagName 메서드는 인수로 전달한 태그 이름을 갖는 모든 요소 노드들을 탐색해 반환한다.
<!DOCTYPE html>
<html>
<body>
<ul>
<li id="apple">Apple</li>
<li id="banana">Banana</li>
<li id="orange">Orange</li>
</ul>
<script>
// 태그 이름이 li인 요소 노드를 모두 탐색하여 반환한다.
// 탐색된 요소 노드들은 HTMLCollection 객체에 담겨 반환된다.
// HTMLCollection 객체는 유사 배열 객체이면서 이터러블이다.
const $elems = document.getElementsByTagName('li');
// 취득한 모든 요소 노드의 style.color 프로퍼티 값을 변경한다.
// HTMLCollection 객체를 배열로 변환하여 순회하며 color 프로퍼티 값을 변경한다.
[...$elems].forEach(elem => { elem.style.color = 'red'; });
</script>
</body>
</html>
함수는 하나의 값만 반환할 수 있으므로 getElementByTagName메서드가 반환하는 DOM 컬렉션 객체인 HTMLCollection 객체는 유사 배열 객체이면서 이터러블이다.
HTML 문서의 모든 요소 노드를 취득하려면 getElementsByTagName 메서드의 인수로 '*'를 전달한다.
// 모든 요소 노드를 탐색하여 반환한다.
const $all = document.getElementsByTagName('*');
// -> HTMLCollection(8) [html, head, body, ul, li#apple, li#banana, li#orange, script, apple: li#apple, banana: li#banana, orange: li#orange]
getElementsByTagName 메서드는 Document.prototype과 Element.prototype 에 정의되어 있다. Document.prototype의 메서드는 DOM의 루트 노드인 document를 통해 호출하며 Element.prototype.getElementsByTagName 메서드는 특정 요소 노드를 통해서호출되며 요소 노드의 자손 노드 중에서 탐색한다.
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
<li>Orange</li>
</ul>
<ul>
<li>HTML</li>
</ul>
<script>
// DOM 전체에서 태그 이름이 li인 요소 노드를 모두 탐색하여 반환한다.
const $lisFromDocument = document.getElementsByTagName('li');
console.log($lisFromDocument); // HTMLCollection(4) [li, li, li, li]
// #fruits 요소의 자손 노드 중에서 태그 이름이 li인 요소 노드를 모두
// 탐색하여 반환한다.
const $fruits = document.getElementById('fruits');
const $lisFromFruits = $fruits.getElementsByTagName('li');
console.log($lisFromFruits); // HTMLCollection(3) [li, li, li]
</script>
</body>
</html>
만약 인수로 전달된 태그 이름을 찾지 못하면 getElementsByTagName 메서드는 빈 HTMLCollection 객체를 반환한다.
class를 이용한 요소 노드 취득
Document.prototype/Element.prototype.getElementsByClassName 메서드는 인수로 전달한 class 어트리뷰트 값을 갖는 모든 요소 노드들을 탐색해 반환한다. 또한 공백으로 구분해 여러 개의 class를 지정할 수 있다.
<!DOCTYPE html>
<html>
<body>
<ul>
<li class="fruit apple">Apple</li>
<li class="fruit banana">Banana</li>
<li class="fruit orange">Orange</li>
</ul>
<script>
// class 값이 'fruit'인 요소 노드를 모두 탐색하여 HTMLCollection 객체에 담아 반환한다.
const $elems = document.getElementsByClassName('fruit');
// 취득한 모든 요소의 CSS color 프로퍼티 값을 변경한다.
[...$elems].forEach(elem => { elem.style.color = 'red'; });
// class 값이 'fruit apple'인 요소 노드를 모두 탐색하여 HTMLCollection 객체에 담아 반환한다.
const $apples = document.getElementsByClassName('fruit apple');
// 취득한 모든 요소 노드의 style.color 프로퍼티 값을 변경한다.
[...$apples].forEach(elem => { elem.style.color = 'blue'; });
</script>
</body>
</html>
getElementsByTageName 메서드와 마찬가지로 Document에 정의된 메서드와 Element에 정의된 메서드간 차이가 있다. Documents에 저장된 메서드는 DOM 전제에서 요소 노드를 탐색해 반환하지만, Element에서 호출하면 특정 요소 노드의 자손 노드 중에서 요소 노드를 탐색하여 반환한다.
<!DOCTYPE html>
<html>
<body>
<ul>
<li class="fruit apple">Apple</li>
<li class="fruit banana">Banana</li>
<li class="fruit orange">Orange</li>
</ul>
<script>
// class 값이 'fruit'인 요소 노드를 모두 탐색하여 HTMLCollection 객체에 담아 반환한다.
const $elems = document.getElementsByClassName('fruit');
// 취득한 모든 요소의 CSS color 프로퍼티 값을 변경한다.
[...$elems].forEach(elem => { elem.style.color = 'red'; });
// class 값이 'fruit apple'인 요소 노드를 모두 탐색하여 HTMLCollection 객체에 담아 반환한다.
const $apples = document.getElementsByClassName('fruit apple');
// 취득한 모든 요소 노드의 style.color 프로퍼티 값을 변경한다.
[...$apples].forEach(elem => { elem.style.color = 'blue'; });
</script>
</body>
</html>
CSS 선택자를 이용한 요소 노드 취득
CSS 선택자는 스타일을 적용하고자 하는 HTML 요소를 특정할 때 사용하는 문법이다.
/* 전체 선택자: 모든 요소를 선택 */
* { ... }
/* 태그 선택자: 모든 p 태그 요소를 모두 선택 */
p { ... }
/* id 선택자: id 값이 'foo'인 요소를 모두 선택 */
#foo { ... }
/* class 선택자: class 값이 'foo'인 요소를 모두 선택 */
.foo { ... }
/* 어트리뷰트 선택자: input 요소 중에 type 어트리뷰트 값이 'text'인 요소를 모두 선택 */
input[type=text] { ... }
/* 후손 선택자: div 요소의 후손 요소 중 p 요소를 모두 선택 */
div p { ... }
/* 자식 선택자: div 요소의 자식 요소 중 p 요소를 모두 선택 */
div > p { ... }
/* 인접 형제 선택자: p 요소의 형제 요소 중에 p 요소 바로 뒤에 위치하는 ul 요소를 선택 */
p + ul { ... }
/* 일반 형제 선택자: p 요소의 형제 요소 중에 p 요소 뒤에 위치하는 ul 요소를 모두 선택 */
p ~ ul { ... }
/* 가상 클래스 선택자: hover 상태인 a 요소를 모두 선택 */
a:hover { ... }
/* 가상 요소 선택자: p 요소의 콘텐츠의 앞에 위치하는 공간을 선택
일반적으로 content 프로퍼티와 함께 사용된다. */
p::before { ... }
Document.prototype/Element.prototype.querySelector 메서드는 인수로 전달한 CSS 선택자를 만족시키는 하나의 요소 노드를 탐색하여 반환한다.
<!DOCTYPE html>
<html>
<body>
<ul>
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</ul>
<script>
// class 어트리뷰트 값이 'banana'인 첫 번째 요소 노드를 탐색하여 반환한다.
const $elem = document.querySelector('.banana');
// 취득한 요소 노드의 style.color 프로퍼티 값을 변경한다.
$elem.style.color = 'red';
</script>
</body>
</html>
Document.prototype/Element.prototype.querySelectorAll메서드는 인수로 전달한 CSS 선택자를 만족시키는 모든 요소 노드를 탐색해 반환한다. querySelectorAll 메서드는 여러 개의 요소 노드 객체를 갖는 DOM 컬렉션 객체인 NodeList 객체를 반환한다.
<!DOCTYPE html>
<html>
<body>
<ul>
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</ul>
<script>
// ul 요소의 자식 요소인 li 요소를 모두 탐색하여 반환한다.
const $elems = document.querySelectorAll('ul > li');
// 취득한 요소 노드들은 NodeList 객체에 담겨 반환된다.
console.log($elems); // NodeList(3) [li.apple, li.banana, li.orange]
// 취득한 모든 요소 노드의 style.color 프로퍼티 값을 변경한다.
// NodeList는 forEach 메서드를 제공한다.
$elems.forEach(elem => { elem.style.color = 'red'; });
</script>
</body>
</html>
HTML 문서의 모든 요소 노드를 취득하려면 querySelctorAll 메서드의 인수로 전체 선택자 '*'를 전달한다.
// 모든 요소 노드를 탐색하여 반환한다.
const $all = document.querySelectorAll('*');
// -> NodeList(8) [html, head, body, ul, li#apple, li#banana, li#orange, script]
특정 요소 노드를 취득할 수 있는지 확인
Element.prototype.matches 메서드는 인수로 전달한 CSS 선택자를 통해 특정 요소 노드를 취득할 수 있는지 확인한다.
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</ul>
</body>
<script>
const $apple = document.querySelector('.apple');
// $apple 노드는 '#fruits > li.apple'로 취득할 수 있다.
console.log($apple.matches('#fruits > li.apple')); // true
// $apple 노드는 '#fruits > li.banana'로 취득할 수 없다.
console.log($apple.matches('#fruits > li.banana')); // false
</script>
</html>
해당 메서드는 이벤트 위임을 사용할 때 매우 유용하다.
HTMLCollection과 NodeList
DOM 컬렉션 객체인 HTMLCollection과 NodeList는 DOM API가 여러 개의 결과값을 반환하기 위한 DOM 컬렉션 객체다. 두 메서드 모두 유사 배열 객체이면서 이터러블이라 for...of문과 스프레드 문법을 사용할 수 있다.
HTMLCollection
getElementsByTagName, getElementsByClassName 메서드가 반환하는 HTMLCollection객체는 노드 객체의 상태 변화를 실시간으로 반영하는 살아 있는 DOM 컬렉션 객체다.
<!DOCTYPE html>
<head>
<style>
.red { color: red; }
.blue { color: blue; }
</style>
</head>
<html>
<body>
<ul id="fruits">
<li class="red">Apple</li>
<li class="red">Banana</li>
<li class="red">Orange</li>
</ul>
<script>
// class 값이 'red'인 요소 노드를 모두 탐색하여 HTMLCollection 객체에 담아 반환한다.
const $elems = document.getElementsByClassName('red');
// 이 시점에 HTMLCollection 객체에는 3개의 요소 노드가 담겨 있다.
console.log($elems); // HTMLCollection(3) [li.red, li.red, li.red]
// HTMLCollection 객체의 모든 요소의 class 값을 'blue'로 변경한다.
for (let i = 0; i < $elems.length; i++) {
$elems[i].className = 'blue';
}
// HTMLCollection 객체의 요소가 3개에서 1개로 변경되었다.
console.log($elems); // HTMLCollection(1) [li.red]
</script>
</body>
</html>
위의 예제를 순서대로 읽어보면 red 클래스를 갖는 세개의 li 요소가 CSS가 변할것 같지만 실제로는 그러지 않다.
1. 첫 번째 반복(i === 0)
$elems[0]은 첫 번째 li 요소이고 이때 red에서 blue로 변경되어 해당 요소는 $elem에서 실시간으로 제거된다.
2. 두 번째 반복 (i === 1)
첫 번째 반복에서 첫 번째로 나온 li요소는 $elems에서 제거되어서 $elem[1]이 가리키는 요소는 세 번째 li 요소가 된다.
3. 세 번째 반복 (i === 2)
이미 두 개의 요소가 사라졌기 때문에 해당 반복문 자체가 실행되지 않는다.
이 경우에는 for문을 역방향으로 순회하거나 while문을 이용해 노트 객체가 남아 있지 않을 때까지 반복하는 방법으로 해결할 수 있다.
// for 문을 역방향으로 순회
for (let i = $elems.length - 1; i >= 0; i--) {
$elems[i].className = 'blue';
}
// while 문으로 HTMLCollection에 요소가 남아 있지 않을 때까지 무한 반복
let i = 0;
while ($elems.length > i) {
$elems[i].className = 'blue';
}
물론 근본적인 해결책은 HTMLCollection 객체를 사용하지 않는것이 좋다. HTMLCollection 객체를 배열로 변환하면 해당 객체를 사용할 필요도 없고 고차 함수 또한 사용할 수 있게 된다.
// 유사 배열 객체이면서 이터러블인 HTMLCollection을 배열로 변환하여 순회
[...$elems].forEach(elem => elem.className = 'blue');
NodeList
HTMLCollection 객체의 부작용을 해결하기 위해 querySelectorAll 메서드를 사용하는 방법도 있다. 해당 메서드는 DOM 컬렉션 객체인 NodeList 객체를 반환하며 실시간으로 노드 객체의 상태 변경을 반영하지 않는 객체다.
// querySelectorAll은 DOM 컬렉션 객체인 NodeList를 반환한다.
const $elems = document.querySelectorAll('.red');
// NodeList 객체는 NodeList.prototype.forEach 메서드를 상속받아 사용할 수 있다.
$elems.forEach(elem => elem.className = 'blue');
NodeList 객체는 NodeList.prototype.forEach 메서드를 상속받아 사용할 수 있으며 배열의 고차함수인 forEach와 사용방법이 동일하다.
NodeList 객체의 대부분은 상태 변경을 실시간으로 반영하지 않지만 childNodes 프로퍼티가 반환하는 NodeList 객체는 HTMLCollection 객체와 같이 실시간으로 노드 객체의 상태 변경을 반영하는 live 객체로 동작하므로 주의가 필요하다.
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
// childNodes 프로퍼티는 NodeList 객체(live)를 반환한다.
const { childNodes } = $fruits;
console.log(childNodes instanceof NodeList); // true
// $fruits 요소의 자식 노드는 공백 텍스트 노드(39.3.1절 "공백 텍스트 노드" 참고)를 포함해 모두 5개다.
console.log(childNodes); // NodeList(5) [text, li, text, li, text]
for (let i = 0; i < childNodes.length; i++) {
// removeChild 메서드는 $fruits 요소의 자식 노드를 DOM에서 삭제한다.
// (39.6.9절 "노드 삭제" 참고)
// removeChild 메서드가 호출될 때마다 NodeList 객체인 childNodes가 실시간으로 변경된다.
// 따라서 첫 번째, 세 번째 다섯 번째 요소만 삭제된다.
$fruits.removeChild(childNodes[i]);
}
// 예상과 다르게 $fruits 요소의 모든 자식 노드가 삭제되지 않는다.
console.log(childNodes); // NodeList(2) [li, li]
</script>
</html>
따라서 노드 객체의 상태 변경과 상관없이 안전하게 DOM 컬렉션을 사용하려면 HTMLCollection이나 NodeList 객체를 배열로 변환하여 사용하는 것을 권장한다.
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
// childNodes 프로퍼티는 NodeList 객체(live)를 반환한다.
const { childNodes } = $fruits;
// 스프레드 문법을 사용하여 NodeList 객체를 배열로 변환한다.
[...childNodes].forEach(childNode => {
$fruits.removeChild(childNode);
});
// $fruits 요소의 모든 자식 노드가 모두 삭제되었다.
console.log(childNodes); // NodeList []
</script>
</html>
'JavaScript > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글
[Deep Dive] DOM - 3 (1) | 2024.09.20 |
---|---|
[Deep Dive] DOM - 2 (1) | 2024.09.10 |
[Deep Dive] 브라우저의 렌더링 과정 (0) | 2024.08.23 |
[Deep Dive] Set과 Map (0) | 2024.08.15 |
[Deep Dive] 디스트럭처링 할당 (0) | 2024.08.07 |