querySelector와 getElementById쓰다보면 드는 생각
아이디를 불러올 때 둘 다 사용가능한데, 무슨 차이가 있지?
알아야 할 개념: HTMLcollection/NodeList
HTMLcollection
https://developer.mozilla.org/ko/docs/Web/API/HTMLCollection
"HTML DOM 내의 HTMLCollection은 문서가 바뀔 때 실시간으로 업데이트됩니다."
HTMLCollection은 리턴 결과가 복수인 경우에 사용하게 되는 객체다
nodeList
https://developer.mozilla.org/ko/docs/Web/API/NodeList
경우에 따라, NodeList는 라이브 콜렉션으로, DOM의 변경 사항을 실시간으로 콜렉션에 반영합니다. 예를 들어, Node.childNodes 는 실시간입니다
다른 경우 NodeList는 정적 콜렉션입니다. DOM을 변경해도 콜렉션 내용에는 영향을 주지 않습니다. document.querySelectorAll() 은 정적 NodeList를 반환합니다.
getElementById는 반환값이 element지만,
tagName과 ClassName은 반환값이 HTMLcollection이다.
querySelectorAll과 getElementsByName은 Nodelist이다.
글 만으로는 정확한 이해가 가지 않는다.
반환값이 다른 3개의 경우를 예제로 확인해보자.
서로같은 div>li+li>li구조로 만들었다.
<div id= "element">
<li>ele</li>
<li>
<li>ele1</li>
</li>
</div>
<div id= "element">
<li>ele</li>
<li>
<li>ele1</li>
</li>
</div>
<hr>
<div name="Node" >
<li>node</li>
<li>
<li>node1</li>
</li>
</div>
<div name="Node" >
<li>node</li>
<li>
<li>node1</li>
</li>
</div>
<hr>
<div class="Html">
<li>html</li>
<li>
<li>html1</li>
</li>
</div>
<div class="Html">
<li>html</li>
<li>
<li>html1</li>
</li>
</div>
</div>
<script>
window.onload= () =>{
const element = document.getElementById('element');
const Node = document.getElementsByName('Node');
const Html = document.getElementsByClassName('Html')
console.log(element, Node, Html)
}
console
프로토타입까지 확인해보니 3개 전부다 다른 메서드를 가지고 있다.
element를 반환하는 경우, 같은 id값이라도 하나의 태그만을 보인다. 즉, unique한 id가 아닐경우는 원하는 태그에 접근하지 못할 수도 있다.
const element = document.getElementById('element');
const Node = document.getElementsByName('Node');
const Html = document.getElementsByClassName('Html') // + tagName
console.log(element, Node, Html)
console.log(element.length) // undefined
console.log("node의 길이입니다==>" + Node.length) //2
console.log("html의 길이입니다===>" + Html.length) //2
console.log("element입니다 ==>" + element.childNodes, element.children) // 67 번
const arrNode = Array.from(Node);
console.log(arrNode.map((v) => v.children)) //70
console.log(arrNode.map((v) => v.childNodes)) //71
const arrHtml = Array.from(Html);
console.log(arrHtml.map((v) => v.children))
먼저 HTML과 NODE모두 유사배열객체로, 배열과 유사한 객체 => 우리가 알던 built-in함수에 대해서 자유로이 쓰지 못하는 상태다(prototype을 확인해보면 쓸 수 있는 메서드를 알 수있다).
따라서 Array.from함수로 얕은 복사를 통해 array형태로 만들어준다.
(Array.from() 메서드는 유사 배열 객체(array-like object)나 반복 가능한 객체(iterable object)를 얕게 복사해 새로운Array 객체를 만듭니다.)
정리 및 새로운 사실
정리하자면, 우리가 쓰는 documen의 id, name, className, queryselector.. 등을 사용할 때 반환 되는 값들이 다르고, id는 element를 반환하기 때문에 유니크한 값이 필요.
id가 아닌 class나 전체 태그에 대해서 배열 속성을 사용하고 싶다면, 반환 값을 살펴보고, 유사배열객체 -> 배열의 형태로 바꿔준다 (Array.from(값))
이 외에 역순으로 for문을 돌린다거나 타개책은 있다고 한다.
*주의해야할점
const Node = document.getElementsByName('Node');
const Html = document.getElementsByClassName('Html') // + tagName
for(let i = 0; i<Html.length ; i++){
Html[i].className= "Html"
console.log('회전')
}
for(let i = 0; i<Node.length ; i++){
Node[i].childNodes[1].className = "hi"
Node[i].childNodes[3].className = "hi"
console.log('회전')
for문을 이용해서 이렇게 className을 바꾼다면 실수다. 위에서 말한 라이브 콜렉션을 생각해야한다. 처음 for문이 한번 돌아간다고 생각하면
Html[0].className= "editClass"
이 되는데, 이때 다시 for문을 돌리면 우리가 생각한
Html[1].className= "editClass"
은 변하지 않는다.
정확하진 않지만, for문을 다시 돌릴 때, 실시간으로 바뀌는 상태에 의해
i = 1을 바라보는데 Html.length는 2->1개가 된다
(첫번째의 className이 editClass로 바뀐 상태기 때문에)
이게 아닐지라도 실시간으로 DOM을 관찰하는 구조는 확실히 에러를 낼 이유가 충분하다.