개발하고 싶은 초심자
[DOM] 자식요소 중 일부를 제외하고 삭제하는 방법 본문
[DOM] 자식요소 중 일부를 제외하고 삭제하는 방법
정새얀 2023. 1. 9. 16:10교육 엔지니어로서 근무하면서, 수강생들의 질문을 받게 되는데 그 질문으로부터 내가 공부를 더 하게 되기도 한다.
DOM의 CRUD에 대해 학습하면서 이런 궁금증을 가져본 적이 없었는데, 질문의 내용과 답변 내용이 나의 학습 수준 향상에 도움이 많이 되어 블로그 글로 정리해보게 되었다.
<body>
<div id="container">
<h2>Title</h2>
<div class="content">1</div>
<div class="content">2</div>
<div class="content">3</div>
<div class="content">4</div>
<div class="content">5</div>
<div class="content">6</div>
</div>
</body>
container 안에서 Title만 남기는 방법으로 for ... of 반복문으로 순회를 하고, remove 메소드를 활용했다.
const container = document.querySelector('#container');
for (const element of container.children) {
if(element.textContent === 'Title') continue;
else element.remove();
}
그러나 이렇게 작성하면 Title은 남겨지지만, remove()메소드를 활용했을 때 일부는 지워지지만 일부는 남게 된다.
이 질문을 보고 mdn과 개발자 도구를 켜고 한참 고민을 했지만, 결국 원인과 해결방법 둘 다 찾지 못했다.
그러던 차에, 한 분이 잘 정리하여 답변을 남겨두어 블로그에 정리해보게 되었다.
1. Element.children은 HTMLCollection을 반환한다.
HTMLCollection 인터페이스는 요소의 문서 내 순서대로 정렬된 일반 컬렉션(arguments처럼 배열과 유사한 객체)을 나타내며... HTML DOM 내의 HTMLCollection은 문서가 바뀔 때 실시간으로 업데이트됩니다....
mdn의 문서에 나와있는 문서의 내용 중 일부다.
HTMLCollection이 배열이 아니고 문서가 바뀔 때 실시간으로 업데이트 된다는 것이다.
(querySelectorAll도 배열을 반환하는 줄 알았는데 이것도 배열이 아니고 NodeList를 반환한다는 것을 알았다...)
이는 순서가 있는 객체이기때문에 for of로 순회할 수 있다.
2. for of로 HTMLCollection의 요소를 DOM에서 삭제하면 발생하는 문제
DOM 내의 HTMLCollection은 문서가 바뀔 때 실시간으로 업데이트되는데, 이 부분이 문제의 원인이다.
위에서 가져온 예시를 그대로 가져왔다.
myFunction() 함수를 클릭 이벤트 안에 넣어주어 Try it이라는 버튼을 클릭할 때마다 myFunction() 안에 있는 내용이 실행될 수 있도록 만든다.
이렇게 했을 때 예상할 수 있는 동작은,
• <li>가 총 6개이므로 for of문은 총 6번 반복하여 순회한다.
• checked된 요소가 6개이기 때문에 DOM에 있는 li는 모두 지워진다.
<body>
<button onClick="myFunction()">Try it</button>
<div id="container">
<h2>Title</h2>
<div class="content">1</div>
<div class="content">2</div>
<div class="content">3</div>
<div class="content">4</div>
<div class="content">5</div>
<div class="content">6</div>
</div>
<script>
const container = document.querySelector("#container");
const contents = container.children;
function myFunction() {
const container = document.querySelector('#container');
for (const ele of container.children) {
if(ele.textContent === 'Title') continue;
else ele.removeChild(contents);
}
console.log(contents);
}
</script>
</body>
하지만 실제 동작은
• 반복문은 3번만 동작한다.
• 따라서 요소는 3개만 삭제된다.
동작을 수행하기전에 콘솔을 한 번 찍고 반복문을 돌면서 콘솔을 찍었다.
반복을 3번만 수행했고, 반복하는 도중에 HTMLCollection이 실시간으로 변경되어 삭제된 인덱스를 뒤에서 채운 것을 확인할 수 있다.
0번 인덱스의 요소를 지우고 1번 인덱스를 갔는데 1번 인덱스의 값이 삭제된 0번 인덱스로 이동한 것이다.
그래서 반복문이 3번만 순회되었고, 밀려내려 온 값들이 삭제되지 않는다.
3. 해결 방법
1. children 대신에 querySelectorAll를 사용한다.
querySelectorAll은 NodeList를 반환하고, 이것은 forEach 메소드를 갖고 있다.
querySelectorAll이 반환한 NodeList는 정적 콜렉션이라 DOM을 변경해도 콜렉션 내용에는 영향을 주지 않는다.
const container = document.querySelector("#container");
const contents = container.querySelectorAll(".content");
function myFunction() {
contents.forEach((c) => {
if (c.textContent === "Title") return;
else container.removeChild(c);
});
}
2. HTMLCollection를 배열로 변경해서 사용하기
const container = document.querySelector("#container");
const contents = container.children;
function myFunction() {
const arrContents = Array.from(contents); // 배열로 만들기
for (let t of arrContents) {
if (t.textContent === "Title") continue;
if (t.firstChild) container.removeChild(t);
}
}
답변에 달린 예시가 질문의 내용과 달라서, 질문에 올라왔던 코드 내용을 살짝 바꾼 후, 최대한 그에 맞는 답의 예시로 바꾸려고 이리저리 해보았다. 같이 해보면서 나도 공부가 되는 것 같았다.
해당 내용을 정리하면서 더 찾아보니, 보통은 배열로 바꾸는 방식을 많이 사용한다고 한다.
수강생 때 DOM에 대해 이해하고 넘어갔다고 생각했는데, 역시 아직도 배울 것들이 너무너무 많았다.
'기술개념정리(in Javascript) > 개발&에러핸들링(in Work(Codestates))' 카테고리의 다른 글
[React] 배열에서 인덱스가 밀리는 이유 (0) | 2023.01.11 |
---|---|
[Error] Jump target cannot cross function boundary (0) | 2023.01.09 |
[HTTP Method] PUT vs PATCH (0) | 2022.12.21 |
[React] useState는 비동기인가 동기인가 (0) | 2022.12.20 |
[React Error]Uncaught TypeError: Cannot read properties of undefined (reading 'map') (0) | 2022.12.14 |