DOM 컬렉션 객체인 HTMLCollection과 NodeList는 DOM API가 여러 개의 결괏값을 반환하기 위한 DOM 컬렉션 객체이다. HTMLCollection과 NodeList는 모두 유사 배열 객체이면서 이터러블이다. 따라서 for... of 문으로 순회할 수 있으며 스프레드 문법을 사용하여 간단히 배열로 변환할 수 있다.
1. HTMLCollection
HTMLCollection은 class, tag에 접근 가능하며 텍스트 노드에는 접근하지 못한다.
getElementsByTagName, getElementsByClassName 메서드가 반환하는 HTMLCollection 객체는 노트 객체의 상태 변화를 실시간으로 반영하는 살아있는 DOM 컬렉션이다. '살아있다'라는 게 어떤 의미인지 생소해서 이해를 돕기 위한 예제가 필요하다.
<!DOCTYPE html>
<head>
<style>
.red {
color: red;
}
.blue {
color: blue;
}
</style>
</head>
<body>
<div>
<p class="red">HTML</p>
<p class="red">CSS</p>
<p class="red">JavaScript</p>
</div>
<script>
const el = document.getElementsByClassName('red');
for(let i=0;i<el.length;i++) {
el[i].className = 'blue';
}
</script>
</body>
</html>
위 예제는 class가 red인 요소들을 blue로 바꿔주는 코드이다. for문으로 순회하며 blue로 바꿔주었지만 'CSS' 텍스트는 바뀌지 않은 걸 볼 수 있다.
그 이유는
- 첫번째 for문이 시작했을 때 첫 요소(el[0])는 'red'에서 'blue'로 변경된다. 이때 el[0]이 더 이상 'red'가 아니므로, getElementsByClassName 메서드의 인자에서 실시간으로 제거된다.
- for문의 두번째 반복은 el[1] 요소에 대해 실행하는데, 이 시점에서 HTMLCollection객체는 'HTML' 텍스트가 인자에서 제거되었으므로 {0: 'CSS' , 1: 'JavaScript' , length : 2} 가 될 것이다. 즉 1번 index인 'JavaScript'의 class를 'blue'로 바꿔주고 끝난다.
- for문의 범위는 i<el.length 이지만, 현재 'HTML' , 'JavaScript'가 제거된 el.length는 1 이므로 for문은 false로 평가되어 반복문이 종료된다.
이처럼 살아있는 객체인 HTMLCollection은 실시간으로 노드 객체의 상태 변경을 반영하여 요소를 제거하기 때문에 의도하지 않은 결과가 나올 수 있다. 해결방법으로 for문을 뒤에서부터 역순으로 돌거나 while문을 사용할 수 있지만, 가장 좋은 방법은 그냥 유사 배열 객체를 배열로 만드는 것이다. 배열로 바꾸게 되면 '살아있는' 상태로 객체를 사용하지 않아도 되고 배열의 고차 함수를 사용할 수 있다.
<script>
const el = document.getElementsByClassName('red');
[...el].forEach(el => el.className = 'blue')
</script>
2. NodeList
HTMLCollection 객체에 위와 같은 문제점이 있다면 querySelectorAll 메서드를 사용해 NodeList 객체를 반환하는 방법이 있다. NodeList 객체는 '살아있지 않은' 객체다. 즉 실시간으로 노드 객체의 상태를 반영하지 않는다. 하지만 childNodes 프로퍼티가 반환하는 NodeList 객체는 '살아있는' 객체로 동작하므로 주의해야 한다.
이 글을 포스팅하게 된 궁극적인 이유는 HTMLCollection, NodeList 모두 유사 배열 객체 이면서 이터러블인 상태는 동일한데 왜 NodeList는 forEach 메서드 사용이 가능하고 HTMLCollection은 불가능한지 궁금했었다. NodeList 객체는 Array.prototype.forEach 메서드와 사용방법이 동일한 NodeList.prototype.forEach 메서드를 상속받기 때문이며 forEach 외에도 item, entries, keys, values 메서드도 사용 가능하다.
NodeList 역시 살아있는 상태가 될 위험이 있으므로 역시 배열로 만들어 사용하는 것을 권한다. HTMLCollection과 NodeList 객체는 모두 유사 배열 객체이면서 이터러블 이므로 스프레드 문법 혹은 Array.from 메서드로 배열로 만들 수 있다.
참조)
deep dive
https://medium.com/@layne_celeste/htmlcollection-vs-nodelist-4b83e3a4fb4b
https://dev.to/jharteaga/difference-between-htmlcollection-and-nodelist-25bp
'Programing > Javascript' 카테고리의 다른 글
[JS] 소셜웹페이지 - 로그인 UI (0) | 2022.08.02 |
---|---|
[JS] 변수 호이스팅 (0) | 2022.07.28 |
[JS] 이터러블과 유사 배열 객체 (0) | 2022.07.17 |
[JS] var vs. let vs. const (0) | 2022.07.15 |
[JS] Array vs. Set (0) | 2022.06.12 |