참고서적: DOM - 잡았다, 요 돔!, DOM 을 깨우치다
DOM 을 보니 좀더 깊이 공부해야할것으로 느껴졌다. 아마 앞의 진도에서는 자바스크립트를 배우며 DOM 역시 같이 사용할 것이다. 더 깊숙히 공부하기 위해 해당 내용을 정리하며, 공부하도록 하자.
본 내용은 공부할 목적으로 적은 내용이므로, 틀린 내용이 포함될 수 있다.
우리가 .list 라는 객체를 가져온다고 생각해보자.
<ul>
<li class="list">item1</li>
<li class="list">item2</li>
<li class="list">item3</li>
</ul>
console.log(document.querySelectorAll(".list"));
// NodeList(3)[li.list, li.list, li.list]
이때, querySelectorAll 을 사용하여 li.list 를 가진 요소 전부를 가져온다.
콘솔로 찍힌 문구로 보면 NodeList 라고 되어있다.
그럼 이번에는 getElementsByClassName 으로 li.list 를 가져와본다.
console.log(document.getElementsByClassName(".list"));
// HTMLCollection(3)[li.list, li.list, li.list]
이번에는 HTMLCollection 의 문구가 콘솔상에 나온다.
여기서 궁금한것 NodeList 와 HTMLCollection 이 무엇인지이다.
이를 이해하기 위해서는 ArrayLike 에 대해 알고 있는것이 좋다.
앞서서 DOMTree 에서 .list 를 가진 요소들을 가져왔다.
이때 해당 노드들의 배열(NodeList 및 HTMLCollection)은 ArrayLike 객체이다.
이 ArrayLike 객체는 마치 Array 처럼 만들어진 객체이다.
다음을 보자.
const arrrayLike = {
"0": "NodeElement1",
"1": "NodeElement2",
"2": "NodeElement3",
"length": 3,
};
// 이 arrayLike 는 마치 배열처럼 사용가능하다.
for (let i = 0; i < arrayLike.length; i += 1) {
console.log(arrayLike[i]);
} // "NodeElement1"
// "NodeElement2"
// "NodeElement3"
하지만, 이 객체는 Obejct 이지 Array 가 아니다.
그러므로, Array 의 method 는 사용불가능하다.
즉, 요소를 담은 배열과 유사한 컬렉션 을 반환한다는 것이다.
그럼 왜 하나의 ArryLike 객체만 있으면 되지, 굳이 두개의 종류가 필요할까?
HTMLCollection의 이름은 현대적DOM의 이전, 구성요소로 HTML 요소만 지닐 수 있었던 시절에 정해졌습니다.
이 말은 HTMLCollection 은 구버전부터 존재했던 collection 이고,
NodeList 는 추후 DOM 이 발전하면서 생긴것같다.
둘의 차이점은 다음과 같다.
| iterable | live | property & method | |
|---|---|---|---|
| HTMLCollection | o | o | HTMLCollection.length HTMLCollection.item() HTMLCollection.namedItem() |
| NodeList | o | o and x | NodeList.length NodeList.entries() NodeList.forEach() NodeList.keys() NodeList.values() |
메서드에 대한 자세한건 아래의 링크에서 살펴보도록 하자.
MDN HTMLCollection
MDN NodeList
Table 만 보더라도 기능이 더 많이 생긴것을 볼 수 있다.
몰랐는데, 둘다 iterable 하다는것은 처음 알았네...
여기서 live 는 큰 차이점 중 하나이다.
live 란 DOM 에 값 반영시 해당 요소에 실시간으로 값이 반영되는 것이다.
HTMLCollection 으로 반환되는 list 는 live 하다.
반면, NodeList 는 특정 상황을 제외하고는, static 하다.
live 즉 객체의 요소들이 실시간으로 살아있다는것은 꽤나 골치아픈 문제를 발생시킨다.
다음을 보자.
<ul>
<li class="list">item1</li>
<li class="list">item2</li>
<li class="list">item3</li>
</ul>
const $lists = document.getElementsByClassName("list");
// HTMLCollection(3)[li.list, li.list, li.list]
for (let i = 0; i < $lists.length; i += 1) {
$lists[i].className = "list2";
}
console.log(document.body.innerHTML);
/*
<ul>
<li class="list2">item1</li>
<li class="list">item2</li>
<li class="list2">item3</li>
</ul>
<script src="index.js"></script>
*/
알수 없는 오류가 발생했다.
이러한 이유는, 실시간 반영(live) 때문이다.
처음 $lists[0]의 요소를 ClassName list2 로 변경할때 실시간 반영되어, $lists.length 가 2 로 된다.
이때, $lists[0] 은 item2 를 가진 li.list 가 되고
$lists[1] 은 item3 을 가진 li.list 가 된다.
그러므로 i=1 이 된 상황에서 item3 을 가진 li.list 가 선택되고, className 을 변경한다.
결국은, item2 를 가진 li는 className 이 변경되지 않은채 존재하게 된다.
위처럼 예상치 못한 결과가 나올수 있으므로, 반복문 사용시 배열로 변경해서 사용하는것이 좋을 것 같다.
반면, static 한 NodeList 는 전혀다른 결과를 가져온다.
실시간 반영이 아니라 현재 문서에 대한 snapshot 이기 때문에, 위처럼 작동하지 않는다.
const $lists = document.querySelectorAll(".list");
// HTMLCollection(3)[li.list, li.list, li.list]
for (let i = 0; i < $lists.length; i += 1) {
$lists[i].className = "list2";
}
console.log(document.body.innerHTML);
/*
<ul>
<li class="list2">item1</li>
<li class="list2">item2</li>
<li class="list2">item3</li>
</ul>
<script src="index.js"></script>
*/
위 상황은 예상가능한 동작을 한다.
위 NodeList 나 HTMLCollection 을 배열로 변경하고 싶다면,
Array.from() 을 사용하면된다.
반면, NodeLIst 역시 live 한 collection 을 반환할 수 있다.
Node.childNodes 가 그렇다.
해당 요소를 반복 순회하는 일이 많다고 싶으면, 그냥 Array 객체로 만든이후 처리하는것이 현명할 듯싶다.
이렇게 오늘은 NodeList 와 HTMLCollection 의 차이를 알아보았다.
위 차이를 알아야 추후 개발시 발생할 수 있는 문제점중 하나로 기억할 수 있게 된것 같다