[JavaScript] NodeList와 HTMLCollection 차이

MOON HEE·2022년 5월 29일
0

여러 개의 요소에 접근할 때 querySelectorAll나 getElementsByClassName 등을 사용한다. 개념이 잘 안잡혀 있어서 습관적으로 querySelector, querySelectorAll을 썼는데, 이렇게 하다가 결국 문제가 생겼다. 오히려 좋아!

0. NodeList와 HTMLCollection이 뭐지??


위 첫번째 사진은 querySelectorAll로 리스트를 잡은 거고, 두번째 사진은 getElementsByTagName으로 리스트를 잡은 거다. 내가 만들고 있는 메모장에 li 삭제 기능을 넣고 싶어서 remove()를 써보다가 잘 안먹어서 [[Prototype]]이 다르다는 것까지 알게 됐다.

우선, NodeList와 HTMLCollection는 각각 querySelector(All), getElementsByTagName으로 요소에 접근할 때 반환하는 객체이다.

그리고 NodeList와 HTMLCollection 모두 유사 배열 객체이면서 이터러블(iterable, 반복 가능한)이다. 따라서 둘 다 length 프로퍼티를 가지므로 객체를 배열처럼 접근할 수 있고 반복문을 돌 수 있다. 하지만 유사 배열 객체이기 때문에 자바스크립트에서 제공하는 배열 객체의 메소드는 사용할 수 없다. (ex. map, forEach, reduce 등등)


<body>
  	    <div class="container">
        <h1>😎 오늘 할 일</h1>
        <input id="inputField" type="text" placeholder="여기에 적어:)">
        <button id="addToDo">추가</button>
        <button id="deleteAll">전체삭제</button>
        <h2>리스트업!</h2>

        <!-- inputField에 할일이 입력되고 + 버튼이 눌리면 할일이 나타나는 공간-->
        <div id="toDoList" class="to-dos"></div>
    </div>
  
	<script>
      	const inputBox = document.getElementById('inputField'); 
        const addToDo = document.getElementById('addToDo'); 
        const toDoList = document.getElementById('toDoList');
        const deleteAll = document.getElementById('deleteAll');
      
      	// 전체삭제 버튼 이벤트
        deleteAll.addEventListener('click', function() {
      
            // i) li를 querySelectorAll로 잡는 방식
            const createdList = document.querySelectorAll('li');
            createdList.forEach(item => {
                item.remove();
            });

            // ii) li를 getElementsByTagName로 잡는 방식
            const createdList = document.getElementsByTagName('li');
            for (let i = createdList.length - 1; i >= 0; --i) {
            	createdList[i].remove();
            }
      	});
    </script>
</body>

1. querySelectorAll로 잡는 방식, NodeList


querySelectorAll을 사용하여 여러 개의 요소 노드객체를 반환할 때 NodeList 객체를 반환한다. NodeList 객체는 노드 객체의 상태 변화를 반영하지 않는 non-live DOM 컬렉션 객체이다. 따라서 NodeList는 노드가 변경되도 그 상태를 반영하지 않는다.
(live DOM 컬렉션 예시에 관해서는 https://yung-developer.tistory.com/m/79 참고)

li 태그를 querySelectorAll로 잡았을 때, 아래 코드로 짜면 구현이 안된다.

const createdList = document.querySelectorAll('li');
createdList.remove(); // 구현이 안된다.

list를 remove하는 코드를 짜려면 remove를 한번 감싸줘야 한다. remove()는 상대적으로 나온지 얼마 안된 DOM 메서드이고 인터넷 익스플로러에서 지원되지 않는다.
remove() MDN documentation

const createdList = document.querySelectorAll('li');
createdList.forEach(item => {
	item.remove();
});

NodeList는 NodeList.prototype.forEach 메서드를 상속받아 사용할 수 있다. 하지만 forEach 외의 Array.prototype에서 제공하는 map, reduce, filter 등의 메서드는 사용할 수 없다.


2. getElementsByTagname으로 잡는 방식, HTMLCollection


getElementsByTagname, getElementsByClassName 메서드가 반환하는 HTMLCollection 객체는 노드 객체의 상태 변화를 실시간으로 반영하는 살아있는 live DOM 컬렉션 객체이다.
(live DOM 컬렉션 예시에 관해서는 https://yung-developer.tistory.com/m/79 참고)

li 태그를 getElementsByTagName로 잡았을 때, 아래 코드로 짜면 구현이 안된다.

const createdList = document.getElementsByTagName('li');
createdList.remove(); // 구현이 안된다.

스택오버플로우에 관련 답변을 찾아봤더니 안 먹는 이유가 있었다. list가 Live하기 때문이다. 해결방법 중 하나는 아래 코드처럼 for문을 사용하여 역순으로 리스트들을 반복하는 것이다.

const createdList = document.getElementsByTagName('li');
for (let i = createdList.length - 1; i >= 0; --i) {
	createdList[i].remove();
}

이 방법 이외에도 배열로 변환해서 해결할 수 있다.
참고로 forEach도 안먹는다. HTMLCollection은 forEach 메서드를 상속받아 사용할 수 없다.


3. 결론



참고 사이트


https://yung-developer.tistory.com/m/79
https://stackoverflow.com/questions/23988982/removing-htmlcollection-elements-from-the-dom

profile
자고 일어나면 해결되는게 더 많은 듯 그럼 잘까

0개의 댓글