-
DOM요소에 접근하고 속성 가져오기JavaScript/DOM 2022. 11. 23. 20:09
HTMl 요소는 렌더링 엔진에 의해 파싱되어 DOM을 구성하는 요소 노드 객체로 변한다
노드 객체들로 구성된 트리 자료 구조를 DOM이라 한다.
노드 객체의 트리로 구조화되어 있기 대문에 DOM을 DOM트리라고 부르기도 한다<div class="greeting">Hello</div> div => 시작태그 (start tag) class => 어트리뷰트 이름(attritute name) gretting => 어트리뷰트 값(attribute value) Hello => 콘텐츠(contents) </div> => 종료태그(end tag)
노드의 종류 12개
요소노드(Element Node)
- HTML구조나 내용 또는 스타일 등을 동적으로 조작하려면 먼저 요소 노드를 취득해야 한다
- 텍스트노드는 요소노드의 자식 노드, 어튜리뷰트 노드는 요소노드와 연결되어 있기 때문에 텍스트노드나 어트리뷰트 노드를 조작하고자 할 때도 요소노드를 조작하면 된다
ex)
h1요소의 텍스를 변경하고 싶다면?!
1. DOM트리에 존재하는 h1 요소 노드 취득하기 ( HTML 요소를 조작하는 시점 )
2. 취득한 요소 노드의 자식 노드인 텍스트 노드 변경하기요소노드를 취득하는 다양한 방법?!?!?!
1. document.getElementById
- id값으로 획득하기
- id는 HTML문서내에서 유일한 값이어야 한다. 그리고 class attribute과 같이 ex) <li id="apple apple2 apple3"> 여러개를 가질 수 없다
- 중복된 id를 갖는 HTML 요소가 여러개 존재하더라도 어떠한 에러도 발생하지 않는다 (이럴경우 첫번째 요소를 반환)
<li id="fruit">사과</li>
<li id="fruit">바나나</li><!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <ul> <li id="apple">사과</li> <li id="banana">바나나</li> <li id="orange">오뤤지</li> </ul> <script> const elem = document.getElementById('orange'); elem.style.color = "blue"; </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <ul> <li id="fruit">사과</li> <li id="fruit">바나나</li> <li id="fruit">오뤤지</li> </ul> <script> const elem = document.getElementById('fruit'); elem.style.color = "red"; </script> </body> </html>
id값이 없는 경우?!
<script> const elem = document.getElementById('apple'); elem.style.color = "red"; </script>
아래와 같은 오류가 난다
Uncaught TypeError: Cannot read properties of null (reading 'style')
2. 태그 이름을 이용한 요소 노드 획득 document.getElementsByTagName
document.getElementsByTagName('태그이름'); => 특정 요소 노드 취득
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <ul> <li id="apple">사과</li> <li id="banana">바나나</li> <li id="orange">오뤤지</li> </ul> <script> // 태그 이름이 li요소 노드를 모두 탐색하여 반환 // 탐색된 요소 노드들은 HTMLCollection 객체에 담겨 반환 // HTMLCollection 객체는 유사 배열 객체이면서 이터러블 const elem = document.getElementsByTagName('li'); console.log(elem); </script> </body> </html>
document.getElementsByTagName('*'); => 모든 요소 노드를 취득
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <ul> <li id="apple">사과</li> <li id="banana">바나나</li> <li id="orange">오뤤지</li> </ul> <script> // HTML 문서의 모든 요소노드를 취득하려면 "*" 를 전달한다 const all = document.getElementsByTagName('*'); console.log(all); </script> </body> </html>
getElementsByTagName 메서드는 Document.prototype에 정의된 메서드와 Element.prototype에 정의된 메서드가 있다
- Document.prototype.getElementsByTagName 메서드는 DOM의 루트노드인 문서노드, 즉 document를 통해 호출하며 DOM 전체에서 요소노드를 탐색하여 반환
- Elements.protytpe.getElementsByTagName 메서드는 특정 요소 노드를 통해 호출하며, 특정 요소 노드의 자손 노드 중에서 요소 노드를 탐색하여 반환<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <ul id="fruits"> <li>Apple</li> <li>Banana</li> <li>Orange</li> </ul> <ul> <li>HTML</li> </ul> <script> // Document.prototype.getElementsByTagName 메서드 const lisFromDocument = document.getElementsByTagName('li'); console.log(lisFromDocument); // Elements.protytpe.getElementsByTagName const fruits = document.getElementById('fruits'); const lisFromFruits = fruits.getElementsByTagName('li'); console.log(lisFromFruits); </script> </body> </html>
3. class를 이용한 요소 노드 획득
- Document.prototype에 정의된 메서드와 Element.prototype에 정의된 메서드가 있다
3-1) Document.prototype.getElementsByClassName메서드
=> DOM의 루트 노드인 문서 노드, 즉 document를 통해 호출하며 DOM 전체에서 요소 노드를 탐색하여 반환
3-2) Element.prototype.getElementsByClassName메서드
=> 특정 요소 노드를 통해 호출하며 특정 요소 노드의 자손 노드 중에서 요소노드를 탐색하여 반환한다
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <ul id="fruits"> <li class="banana">banana</li> <li class="apple">apple</li> <li class="orange">orange</li> </ul> <div class="banana">Banana</div> <script> // Dom전체에서 class값이 banana인 요소 노드를 모두 탐색하여 반환 const bananasFromDocument = document.getElementsByClassName('banana'); console.log(bananasFromDocument); // Fruits 요소의 자손 노드 중에서 class값이 banana인 요소 노드를 모두 탐색하여 반환 const fruits = document.getElementById('fruits') const bananasFromFruits = fruits.getElementsByClassName('banana'); console.log(bananasFromFruits); </script> </body> </html>
4. CSS 선택자를 이용한 요소 노드 획득
- css 선택자는 스타일을 적용하고자 하는 HTML요소를 특정할 때 사용하는 문법
- Document.prototype에 정의된 메서드와 Element.prototype에 정의된 메서드가 있다
- Document.prototype에 정의된 메서드는 DOM의 루트 노드인 문서노드, 즉 document를 통해 호출하며, DOM전체에서 요소 노드를 탐색하여 반환한다 - Elemenet.prototype에 정의된 메서드는 특정 요소 노드를 통해 호출하며 특정 요소 노드의 자손 노드 중에서 요소노드를 탐색하여 반환한다
- querySelector, querySelectorAll메서드는 getElementBy*** 보다 다소 느리지만, 좀 더 구체적인 조건으로 요소를 취할 수 있다.
- id어트리뷰트가 있는 요소 노드를 취득하는 경우에는 getElementById를 사용, 그 외에는 querySelector, querySelectorAll를 사용한다
Document.prototype/Element.prototype.querySelector
- 인수로 전달한 CSS선택자를 만족시키는 하나의 요소 노드를 탐색하여 반환한다 - 인수로 전달한 CSS 선택자를 만족시키는 요소 노드가 여러 개인 경우 첫 번째 요소 노드만 반환 - 인수로 전달된 CSS 선택자를 만족시키는 요소 노드가 존재하지 않는 경우 null 반환 - 인수로 전달한 CSS 선택자가 문법에 맞지 않는 경우 DOMException 에러가 발생
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <ul id="fruits"> <li class="banana">banana</li> <li class="apple">apple</li> <li class="orange">orange</li> </ul> <script> const elem = document.querySelector('.banana'); elem.style.color= 'red'; </script> </body> </html>
Document.prototype/Element.prototype.querySelectorAll
- 인수로 전달한 CSS선택자를 만족시키는 모든 요소 노드를 탐색하여 반환한다 - 여러개의 요소 노드 객체를 갖는 DOM 컬렉션 객체인 NodeList객체를 반환한다 (NodeList 객체는 유사 배열 객체이면서 이터러블이다) - 인수로 전달된 CSS선택자를 만족시키는 요소가 존재하지 않는 경우 빈 NodeList객체를 반환한다 - 인수로 전달한 CSS선택자가 문법에 맞지 않는 경우 DOMException 에러가 발생한다
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <ul> <li class="banana">banana</li> <li class="apple">apple</li> <li class="orange">orange</li> </ul> <script> // ul 요소의 자식 요소인 li 요소를 모두 탐색하며 반환 const elems = document.querySelectorAll('ul > li'); console.log(elems); // NodeList(3) </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <ul> <li class="banana">banana</li> <li class="apple">apple</li> <li class="orange">orange</li> </ul> <script> // HTML내의 모든 요소를 탐색하여 반환 const elems = document.querySelectorAll('*'); console.log(elems); // NodeList(12) [html, head, meta, meta, meta, title, body, ul, li.banana, li.apple, li.orange, script] </script> </body> </html>
5. 특정 요소 노드를 취득할 수 있는지 확인
Element.prototype.matches
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <ul id="fruits"> <li class="banana">banana</li> <li class="apple">apple</li> <li class="orange">orange</li> </ul> <script> const apple = document.querySelector('.apple'); console.log(apple.matches('#fruits > li.apple')) // true console.log(apple.matches('#fruits > li.banana')) // false </script> </body> </html>
6. HTMLCollection과 NodeList
- DOM컬렉션 객체인 HTMLCollection과 NodeList는 DOM API가 여러 개의 결과값을 반환하기 위한 DOM컬렉션 객체
- HTMLCollection과 NodeList는 모두 유사 배열 객체이면서 이터러블
- HTMLCollection과 NodeList의 중요한 특징은 노드 객체의 상태 변화를 실시간으로 반영하는 살아있는 객체. HTMLCollection은 언제나 live객체로 동작한다. 단, NodeList는 대부분의 경우 노드 객체의 상태 변화를 실시간으로 반영하지 않고 과거의 정적 상태를 유지하는 non-live 객체로 동작하지만 경우에 따라 live객체로 동작할 때가 있다
6-1) HTMLCollection
- getElementsByTagName, getElementsByClassName 메서드가 반환하는 HTMLCollection 객체는 노드 객체의 상태 변화를 실시간으로 반영하는 살아있는 DOM 컬렉션 객체이다. 따라서 HTMLCollection 객체를 살아있는 객체라고 부르기도 한다
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <ul id="fruits"> <li class="red">banana</li> <li class="red">apple</li> <li class="red">orange</li> </ul> <script> const elems = document.getElementsByClassName('red'); console.log(elems); // HTMLCollection(3) [li.red, li.red, li.red] for(let i=0; i< elems.length; i++){ elems[i].className = 'blue'; } console.log(elems); // HTMLCollection [li.red] </script> </body> </html>
두번째 li class는 blue로 바뀌지 않았다 ! why?!
1. 첫번째반복(i === 0) - elems[0]은 첫번째 li요소. 즉, Apple. 이 요소는 className 프로퍼티에 의해 class값이 red => blue로 변경 - 이때, getElementsByClassName 메서드로 전달한 red와는 더는 일치하지 않기 때문에 elems에 실시간으로 제거 2. 두번째반복(i === 1) - 첫번째 반복에서 li요소는 elems에서 제거되었다. 따라서 elems[1]은 세번째 요소. 즉, Orange. 이 세 번째 li요소의 class값도 blue로 변경되고 마찬가진로 HTMLCollection객체인 elems 에서 실시간으로 제거 3. 세번째반복(i === 2) - elems에는 두 번재 li요소만 남음. 즉,banana. elems.length는 1, i는 2이므로 i< elems.length가 false로 평가되어 반복이 종료. 따라서 li요소의 class값은 변경되지 않는다
HTMLCollection객체는 실시간으로 노드 객체의 상태 변경을 반영하여 요소를 제거할 수 있기 때문에 HTMLCollection
객체를 for문으로 순회하면서 노드 객체의 상태를 변경할 때 주의해야 한다
How to solve it?!
for문을 역방향으로 하거나 아니면 while문을 사용하자!
// 역방향 for문 for(let i = elems.length-1; i>= 0; i--){ elems[i].className = 'blue'; } // while문 let i=0; while(elems.length > i){ elems[i].className = 'blue'; }
HTMLCollection 객체(유사배열)를 사용하지 않고, HTMLCollection 객체를 배열로 변환하면은 여러가지 함수(forEach, map, filter, reduce 등)를 사용할 수 있다
6-2) NodeList
- querySelectorAll 메서드를 사용하면은 DOM 컬렉션 객체인 NodeList객체를 반환한다. 이 때, NodeList 객체는 실시간으로 노드 객체의 상태 변경을 반영하지 않는(non-live) 객체
- 하지만 childNodes프로퍼티가 반환하는 NodeList객체는 HTMLCollection객체와 같이 실시간으로 노드 객체의 상태 변경을 반영하는(live) 객체
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <ul id="fruits"> <li>Apple</li> <li>Banana</li> </ul> <script> const fruits = document.getElementById('fruits'); const {childNodes} = fruits; console.log(childNodes instanceof NodeList); // true console.log(childNodes); // NodeList(5) [text, li, text, li, text] for(let i=0; i< childNodes.length; i++){ fruits.removeChild(childNodes[i]); } console.log(childNodes); // NodeList(2) [li, li] </script> </body> </html>
length가 5이기 때문에, i는 4까지있다 0을 지우면 [li, text, li, text] 1를 지우면 [li, li, text] 2를 지우면 [li,li] indexOutOfRange가 되기때문에 [li, li]가 남는다
Summary
- HTMLCollection 이나 NodeList객체는 예상과 다르게 동작할 때가 있어서 다루기 까다롭고 실수가 쉽다. 따라서 노드 객체의 상태 변경과 상관없이 안전하게 DOM컬렉션을 사용하려면 HTMLCollection이나 NodeList객체를 배열로 변환하여 사용하는 것을 권장. 배열로 변환하면은 여러가지 함수(forEach, Map, filter, reduce 등)을 사용할 수 있다
'JavaScript > DOM' 카테고리의 다른 글
DOM조작 (0) 2022.11.26 DOM - 노드 (0) 2022.11.26 스타일(css style)다루기 (0) 2022.11.22 속성(Attribute)와 프러퍼티(Property) (0) 2022.11.21 DOM(Document Object Model)이란?! (0) 2022.11.19