DOM
- Document Object Mode
- HTML 문서의 계층적 구조와 정보를 표현하며 이를 제어할 수 있는 API , 즉 프로퍼티와 메서드를 제공하는 트리 자료구조
39.1 노드
39.1.1 HTML 요소와 노드 객체
HTML 요소
- HTML 문서를 구성하는 개별적인 요소
- HTML 요소는 렌더링 엔진에 의해 파싱되어 DOM을 구성하는 요소 노드 객체로 변환
- HTML 문서는 HTML 요소들의 집합으로 이뤄지며, HTML 요소는 중첩 관계를 가짐
- HTML 요소 간의 부자 관계를 반영하여 HTML 문서의 구성 요소인 HTML 요소를 객체화한 모든 노드 객체들을 트리 자료구조로 구성
트리 자료구조
- 부모 노드(parent node)와 자식 노드 (child node)로 구성되어 노드 간의 계층적 구조를 표현하는 비선형 자료구조
- 최상위 노드 = 루트 노드이며 루트노드는 0개 이상의 자식노드를 가짐
- 리프 토드 = 자식 노드가 없는 노드
- DOM = 노드 객체들로 구성된 트리 자료구조
39.1.2 노드 객체의 타입
- 노드 객체는 종류가 있고 상속구조를 가짐
- 노드 객체는 총 12개의 종류가 있음
1) 문서 노드 (document node)
- DOM 트리의 최상위에 존재하는 루트 노드로서 document 객체를 가리킴
- document 객체는 브라우저가 렌더링한 HTML 문서 전체를 가라키는 객체로서 전역 객체 window의 document 프로퍼티에 바인딩되어 있음
- 브라우저 환경의 모든 자바스크립트 코드는 script 태그에 의해 분리되어 있어도 하나의 전역 객체 window를 공유
( HTML 문서당 document 객체는 유일)
2) 요소 노드(element node)
- HTML 요소를 가리키는 객체
- HTML 요소 간의 중첩에 의해 부자 관계를 가지며, 이 부자 관계를 통해 정보를 구조화
- 요소 노드는 문서의 구조를 표현
3) 어트리뷰트 노드 (attribute node)
- HTML 요소의 어트리뷰트를 가리키는 객체
- 어트리뷰트 노드는 부모노드가 없으므로 형제(silibing)노드가 아님
- 어트리뷰트 노드에 접근하여 어트리뷰트를 참조하거나 변경하려면 먼저 요소 노드에 접근해야 함
4) 텍스트 노드 (Text node)
- HTML 요소의 텍스트를 가리키는 객체
- 문서의 정보를 표현
- 요소노드의 자식 노드이며 리프노드
39.1.3 노드 객체의 상속 구조
- DOM을 구성하는 노드 객체는 브라우저 환경에서 추가적으로 제공하는 호스트 객체 (host object)
- 노드 객체도 자바스크립트 객체이므로 프로토타입에 의한 상속 구조를 가짐
- 모든 노드 객체는 Object, EventTarget, Node 인터페이스를 상속 받음
- 요소 노드는 Element 인터페이스를 상속받음
- 프로토 체인 관점
ex) input 요소를 파싱하여 객체화한 input 요소 노드 객체는 HTMLInputElement, HTMLElement, Element, Node, EventTarget, Object의 prototype에 바인딩되어 있는 프로토타입 객체를 상속 받음
input 요소 노드 객체의 특성 | 프로토타입을 제공하는 객체 |
객체 | Object |
이벤트를 발생시키는 객체 | EventTarget |
트리 자료구조의 노드 객체 | Node |
브라우저가 렌더링할 수 있는 웹 문서의 요소(HTML,XML,SVG)을 표현하는 객체 | Element |
웹 문서 요소 중에서 HTML 요소를 표현하는 객체 | HTMLEIement |
HTML 요소 중에서 input 요소를 표현하는 객체 | HTMLInputElement |
- 노드 객체는 공통된 기능일수록 프로토타입 체인의 상위에, 개별적인 고유 기능일수록 프로토타입 체인의 하위에 프로토타입 체인을 구축하여 노드 객체에 필요한 기능, 즉 프로퍼티와 메서드를 제공하는 상속 구조를 가짐
- DOM은 HTML 문서의 계층적 구조와 정보를 표현하는 것은 물론 노드 객체의 종류, 즉 노드 타입에 따라 필요한 기능을 프로퍼티와 메서드의 집합인 DOM API로 제공. 이 DOM API를 통해 HTML의 구조나 내용 또는 스타일 등을 동적으로 조작할 수 있음
39.2 요소 노드 취득
- HTML의 구조나 내용 또는 스타일 등을 동적으로 조작하려면 먼저 요소 노드를 취득해야 함
39.2.1 id를 이용한 요소 노드 취득
- Document.prototype.getElementById 메서드는 인수로 전달한 id 어트리뷰트 값을 갖는 하나의 요소를 탐색하여 반환
- Document.prototype의 프로퍼티이므로 문서 노드인 document를 통해 호출해야 함
- 중복된 id값을 갖는 요소가 여러개 존재한다면 첫 번째 요소 노드만 반환
- 인수로 전달된 id 값을 갖는 HTML 요소가 존재하지 않는다면 null을 반환
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8">
<link rel="stlyesheet" href = "style.css">
</head>
<body>
<ul>
<li id="apple"> Apple</li>
<li id="banana">Banana</li>
<li id="orange">Orange</li>
</ul>
<script>
//id 값이 'banana'인 요소 노드를 탐색하여 반환
//두 번째 li 요소가 파싱되어 생성된 요소 노드가 반환
const $elem = document.getElementById('banana');
//취득한 요소 노드의 style.color프로퍼티 값을 변경
$elem.style.color = 'red';
</script>
</body>
</html>
- HTML 요소에 id 어트리뷰트를 부여하면 id 값과 동일한 이름의 전역 변수가 암묵적으로 선언되고 해당 노드 객체가 할당되는 부수 효과 존재
- id 값과 동일한 이름의 전역 변수가 이미 선언되어 있으면 이 전역 변수에 노드 객체가 재할당되지 않음
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8">
<link rel="stlyesheet" href = "style.css">
</head>
<body>
<div id = "foo"></div>
<script>
//id값과 동일한 이름의 전역 변수가 암묵적으로 선언되고 해당 노드 객체가 할당
console.log(foo === document.getElementById('foo')); //true
//암묵적 전역으로 생성된 전역 프로퍼티는 삭제되지만 전역 변수는 삭제되지 않음
delete foo;
console.log(foo); //<div id="foo"></div>
</script>
</body>
</html>
39.2.2 태그 이름을 이용한 요소 노드 취득
- Document.prototype/Element.prototype.getElementByTagName 메서드는 인수로 전달한 태그 이름을 갖는 모든 요소 노드들을 탐색하여 반환
- 여러 개의 노드 객체를 갖는 DOM 컬렉션 객체인 HTMLCollection 객체를 반환
- HTMLCollection 객체는 유사 배열 객체이면서 이터러블
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8">
<link rel="stlyesheet" href = "style.css">
</head>
<body>
<ul>
<li id="apple">Apple</li>
<li id="banana">Banana</li>
<li id="orange">Orange</li>
</ul>
<script>
//태그 이름이 li인 요소 노드를 모두 탐색하여 반환
//탐색된 요소 노드들은 HTMLCollection 객체에 담겨 반환
//HTMLCollection 객체는 유사 배열 객체이면서 이터러블
const $elems = document.getElementsByTagName('li');
//취득한 모든 요소 노드의 style.color 프로퍼티 값을 변경
//HTMLCollection 객체를 배열로 변환하여 순회하며 color 값을 변경
[...$elems].forEach(elem => {elem.style.color='red'};);
</script>
</body>
</html>
- HTML 문서의 모든 요소 노드를 취득하려면 getElementByTagNames 메서드의 인수로 '*'를 전달
//모든 요소 노드를 탐색하여 반환
const $all = document.getElementByTagName('*');
Document.prototype.getElementsByTagName | document를 통해 호출하며 DOM 전체에서 요소 노드를 탐색하여 반환 |
Element.prototype.getElementsByTagName | 특정 요소 노드를 통해 호출하며, 특정 요소 노드의 자손 노드 중에서 요소 노드를 탐색하여 반환 |
39.2.3 class를 이용한 요소 노드 취득
- Documet.prototype/Element.prototype.getElementsByClassName 메서드는 인수로 전달한 class 어트리뷰트 값을 갖는 모든 요소들을 탐색하여 반환
- 여러 개의 요소 노드 객체를 갖는 DOM 컬렉션 객체인 HTMLColleciton 객체를 반환
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8">
<link rel="stlyesheet" href = "style.css">
</head>
<body>
<ul>
<li id="fruit apple">Apple</li>
<li id="fruit banana">Banana</li>
<li id="fruit orange">Orange</li>
</ul>
<script>
//class 값이 'fruit'인 요소 노드를 모두 탐색하여 HTMLCollection 객체에 담아 반환
const $elems = document.getElementsByClassName('fruit');
//취득한 모든 요소의 CSS color 프로퍼티 값을 변경
[...$elems].forEach(elem => {elem.style.color = 'red';});
//class 값이 'fruit apple'인 요소 노드를 모두 탐색하여 HTMLCollection 객체에 담아 반환
const $apples = document.getElementsByClassName('fruit apple');
//취득한 모든 요소의 style.color 프로퍼티 값을 변경
[...$apples].forEach(elem => {elem.style.color = 'blue';});
</script>
</body>
</html>
Document.prototype.getElementsByClassName | document를 통해 호출하며 DOM 전체에서 요소 노드를 탐색하여 반환 |
Element.prototype.getElementsByClassName | 특정 요소 노드를 통해 호출하며 특정 요소 노드의 자손 노드 중에서 요소 노드를 탐색하여 반환 |
39.2.4 CSS 선택자를 이용한 요소 노드 취득
-CSS 선택자는 스타일을 적용하고자 하는 HTML 요소를 특정할 때 사용하는 문법
//전체 선택자 : 모든 요소를 선택
* {...}
//태그 선택자 : 모든 p 태그 요소를 모두 선택
p {...}
//id 선택자 : id 값이 'foo'인 요소를 모두 선택
#foo {...}
//class 선택자 : class 값이 'foo'인 요소를 모두 선택
.foo {...}
//어트리뷰트 선택자 :input 요소 중에 type 어트리뷰트 값이 'text'인 요소를 모두 선택
input[type=text] {...}
//후손 선택자: div 요소의 후손 요소 중 p 요소를 모두 선택
div p {...}
//자식 선택자 : div 요소의 자식 요소 중 p요소를 모두 선택
div > p {...}
//인접 형제 선택자 : p 요소의 형제 요소 중에 p 요소 바로 뒤에 위치하는 ul 요소를 선택
p + ul {...}
//일반 형제 선택자 : p 요소의 형제 요소 중에 p 요소 뒤에 위치하는 ul 요소를 모두 선택
p ~ ul {...}
//가상 클래스 선택자 : hover 상태인 a 요소를 모두 선택
a:hover {...}
//가상 요소 선택자 : p 요소의 콘텐츠의 앞에 위치하는 공간을 선택
//일반적으로 content 프로퍼티와 함께 사용
p :: before {...}
- Document.prototype/Element.prototype.querySelector 메서드는 인수로 전달한 CSS 선택자를 만족시키는 하나의 요소를 탐색하여 반환
- 인수로 전달한 CSS 선택자를 만족시키는 요소 노드가 여러 개인 경우 첫 번째 요소 노드만 반환
- 인수로 전달된 CSS 선택자를 만족시키는 요소 노드가 존재하지 않는 경우 null을 반환
- 인수로 전달한 CSS 선택자가 문법에 맞지 않는 경우 DOMException 에러 발생
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8">
<link rel="stlyesheet" href = "style.css">
</head>
<body>
<ul>
<li id="fruit apple">Apple</li>
<li id="fruit banana">Banana</li>
<li id="fruit orange">Orange</li>
</ul>
<script>
// class 어트리뷰트 값이 'banana'인 첫 번째 요소 노드를 탐색하여 반환
const $elem = document.querySelector('.banana');
//취득한 요소 노드의 style.color 프로퍼티 값을 변경
$elem.style.color ='red';
</script>
</body>
</html>
- querySelectorAll 메서드는 인수로 전달한 CSS 선택자를 만족시키는 모든 요소 노드를 탐색하여 반환
- 여러 개의 요소 노드 객체를 갖는 DOM 컬렉션 객체인 NodeList 객체를 반환
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8">
<link rel="stlyesheet" href = "style.css">
</head>
<body>
<ul>
<li id="fruit apple">Apple</li>
<li id="fruit banana">Banana</li>
<li id="fruit orange">Orange</li>
</ul>
<script>
//ul 요소의 자식 요소인 li 요소를 모두 탐색하여 반환
const $elems = document.querySelectorAll('ul>li');
//취득한 요소 노드들은 NodeList 객체에 담겨 반환
console.log($elems); //NodeList(3) [li.apple,li.banana,li.orange]
//취득한 모든 요소 노드의 style.color 프로퍼티 값을 변경
//NodeList는 forEach 메서드를 제공
$elems.forEach(elem => {elem.style.color = 'red';});
</script>
</body>
</html>
Document.prototype.querySelector | document를 통해 호출하며 DOM 전체에서 요소 노드를 탐색하여 반환 |
Element.prototype.querySelector | 특정 요소 노드를 통해 호출하며 특정 요소 노드의 자손 노드 중에서 요소노드를 탐색하여 반환 |
39.2.5 특정 요소 노드를 취득할 수 있는지 확인
- Element.prototype.matches 메서드는 인수로 전달한 CSS 선택자를 통해 특정 요소 노드를 취득할 수 있는지 확인
- 이벤트 위임을 사용할 떄 유용
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8">
<link rel="stlyesheet" href = "style.css">
</head>
<body>
<ul>
<li id="fruit apple">Apple</li>
<li id="fruit banana">Banana</li>
<li id="fruit orange">Orange</li>
</ul>
<script>
const $apple = document.querySelector('.apple');
//$apple 노드는 '#fruits > li.apple'로 취득 할 수 있다
console.log($apple.mathches('#fruits > li.apple'));
//$apple 노드는 '#fruits > li.banana'로 취득 할 수 없다
console.log($apple.matches('#fruits>li.banana'));
</script>
</body>
</html>
39.2.6 HTMLCollection과 NodeList
- HTMLCollection과 NodeList는 DOM API가 여러 개의 결과값을 반환하기 위한 DOM 컬렉션 객체
- 모두 유사 배열 객체이면서 이터러블
- 노드 객체 상태 변화를 실시간으로 반영하는 살아있는(live) 객체
- HTMLColleciton은 언제나 live 객체로 동작
- NodeLIst는 대부분의 경우 노드 객체의 상태 변활르 실시간으로 반영하지 않고 과거의 정적 상태를 유지하는 non-live 객체로 동작하지만 경우에 따라 live 객체로 동작할 때가 있음
1) HTMLCollection
- 노드 객체의 상태 변화를 실시간으로 반영하는 살아 있는(live) DOM 컬렉션 객체
- 실시간으로 노드 객체의 상태 변경을 반영하여 요소를 제거할 수 있기 때문에 HTMLCollection 객체를 for문으로 순회하면서 노드 객체의 상태를 변경해야 할 때는 주의 해야함
2) NodeList
- 실시간으로 노드 객체의 상태 변경을 반영하지 않는 (non-live) 객체
- But, ChildNodes 프로퍼티가 반환하는 NodeList 객체는 실시간으로 노드 객체의 상태 변경을 반영하는 live 객체로 동작
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8">
<link rel="stlyesheet" href = "style.css">
</head>
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
</ul>
<script>
const $fruits = document.getElementById('fruits');
//childNodes 프로퍼티는 NodeList 객체(live)를 반환
const { childNodes } = $fruits;
console.log(childNodes instanceof NodeList); //true
//$fruits 요소의 자식 노드는 공백 텍스트 노드를 포함해 5개
console.log(childNodes); //NodeList(5) [text,li,text,li,text]
for(let i = 0; i < childNodes.length; i++) {
//removeChild 메서드가 호출될 떄마다 NodeList 객체인 childNodes가 실시간으로 변경
//따라서 첫 번쨰, 세 번째, 다섯 번째 요소만 삭제
$fruits.removeChild(childNodes[i]);
}
//예상과 다르게 $fruits 요소의 모든 자식 노드가 삭제되지 않음
console.log(childNodes); //NodeList(2) [li,li]
</script>
</body>
</html>
- 노드 객체의 상태 변경과 상관없이 안전하게 DOM 컬렉션을 사용하려면 HTMLCollection이나 NodeList 객체를 배열로 변환하여 사용하는 것을 권장
39.3 노드 탐색
- DOM 트리 상위 노드를 탐색할 수 있도록 Node, Element 인터페이스는 트리 탐색 프로퍼티를 제공\
- 노드 탐색 프로퍼티는 setter 없이 getter만 존재하여 참조만 가능한 읽기 전용 프로퍼티
Node.prototype이 제공 | prarentNode, previousSibling, firstChild, childNodes 프로퍼티 |
Element.prototype이 제공 | previousElementSibling, nextElementSibling, children 프로퍼티 |
39.3.1 공백 텍스트 노드
- HTML 요소 사이의 스페이스, 탭, 줄바꿈(개행) 등의 공백(white space)문자는 텍스트 노드를 생성
39.3.2 자식 노드 탐색
프로퍼티 | 설명 |
Node.prototype.childNodes | - 자식 노드를 모두 탐색하여 DOM 컬렉션 객체인 NodeList에 담아 반환 - childNodes 프로퍼티가 반환한 NodeList에는 요소 노드 쭌만아니라 텍스트 노드도 포함되어 있을 수 있음 |
Element.prototype.children | - 자식 노드 중에서 요소 노드만 모두 탐색하여 DOM 컬렉션 객체인 HTMLCollection에 담아 반환 - children 프로퍼티가 반한한 HTMLCollection에는 텍스트 노드가 포함되지 않는다 |
Node.prototype.firstChild | - 첫 번째 자식 노드를 반환 - firstChild 프로퍼티가 반환한 노드는 텍스트 노드이거나 요소 노드 |
Node.prototype.lastChild | - 마지막 자식 노드를 반환 - lastChild 프로퍼티가 반환한 노드는 텍스트 노드이거나 요소 노드 |
Element.prototype.firstElementChild | - 첫 번째 자식 노드를 반환 - firstElementChild 프로퍼티는 요소 노드만 반환 |
Element.prototype.lastElementChild | - 마지막 자식 노드를 반환 - lastElementChild 프로퍼터는 요소 노드만 반환 |
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8">
<link rel="stlyesheet" href = "style.css">
</head>
<body>
<ul id="fruits">
<li class = "apple">Apple</li>
<li clsss = "banana">Banana</li>
<li class = "orange">Orange</li>
</ul>
<script>
//노드 탐색의 기점이 되는 #fruis 요소 노드를 취득
const $fruits = document.getElementById('fruits');
console.log($fruits.childNodes);
//NodeList(7) [text, li.apple, text, li.banana, text, li.orante, text]
console.log($fruits.children);
//HTMLCollection(3) [li.apple, li.banana, li.orange]
console.log($fruits.fristChild);
//#text
console.log($fruits.lastChild);
//#text
console.log($fruits.firstElementChild);
//li.apple
console.log($fruits.lastElementChild);
//li.orange
</script>
</body>
</html>
39.3.3 자식 노드 존재 확인
- Node.prototype.hasChildNodes 메서드를 사용하여 자식 노드가 존재하는지 확인
- 자식 노드가 존재하면 true, 자식 노드가 존재하지 않으면 false를 반환
- 택스트 노드를 포함하여 자식노드의 존재 확인
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8">
<link rel="stlyesheet" href = "style.css">
</head>
<body>
<ul id="fruits">
</ul>
<script>
//노드 탐색의 기점이 되는 #fruits 요소 노드를 취득
const $fruits = document.getElementById('fruits');
//hasChildNodes 메서드는 텍스트 노드를 포함하여 자식 노드의 존재를 확인
console.log($fruits.hasChildNodes()); //ture
</script>
</body>
</html>
- 자식 노드 중에 텍스트 노드가 아닌 요소 노드가 존재하는지 확인하려면 children.length 또는 Element 인터페이스의 childElementCount 프로퍼티를 사용
conosle.log(!!$fruits.children.length); //false
console.log(!!$fruits.childElementCount); //false
39.3.4 요소 노드의 텍스트 노드 탐색
- 요소 노드의 텍스트 노드는 요소 노드의 자식 노드
- 요소 노드의 텍스트 노드는 firstChild 프로퍼티로 접근 가능
<div id = "foo">Hello</div>
<script>
//요소 노드의 텍스트 노드는 firstChild 프로퍼티로 접근할 수 있다
console.log(document.getElementById('foo').fristChild); //#text
</script>
39.3.5 부모 노드 탐색
- Node.prototype.parentNode 프로퍼티를 사용하여 부모 노드 탐색
- 텍스트 노드는 리프 노드이므로 부모 노드가 텍스트 노드인 경우는 없음
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8">
<link rel="stlyesheet" href = "style.css">
</head>
<body>
<ul id="fruits">
<li class = "apple">Apple</li>
<li class = "banana">Banana</li>
<li class = "orange">Orange</li>
</ul>
<script>
//노드 탐색의 기점이 되는 .banana 요소 노드를 취득한다
const $banana = document.querySelector('.banana');
//.banana 요소 노드의 부모 노드를 탐색
console.log($banana.parentNode); //ul#fruits
</script>
</body>
</html>
39.3.6 형제 노드 탐색
- 어트리뷰트 노드는 요소 노드와 연결되어 있지만 부모 노드가 같은 형제 노드가 아니므로 반환되지 않음
- 텍스트 노드 또는 요소 노드만 반환
프로퍼티 | 설명 |
Node.prototype.previousSibling | - 부모 노드가 같은 형제 노드 중에서 자신의 이전 형제 노드를 탐색하여 반환 - 요소 노드 + 텍스트 노드 |
Node.prototype.nextSibling | - 부모 노드가 같은 형제 노드 중에서 자신의 다음 형제 노드를 탐색하여 반환 - 요소 노드 + 텍스트 노드 |
Element.prototype.previousElementSibling | - 부모 노드가 같은 형제 노드 중에서 자신의 이전 형제 요소 노드를 탐색하여 반환 - 요소 노드만 |
Element.prototype.nextElementSibling | - 부모 노드가 같은 형제 노드 중에서 자신의 다음 형제 노드를 탐색하여 반환 - 요소 노드만 |
<!DOCTYPE html>
<html>
<head>
<meta charset = "UTF-8">
<link rel="stlyesheet" href = "style.css">
</head>
<body>
<ul id="fruits">
<li class = "apple">Apple</li>
<li class = "banana">Banana</li>
<li class = "orange">Orange</li>
</ul>
<script>
//노드 탐색의 기점이 되는 #fruits 요소 노드를 취득
const $fruits = document.getElementById('fruits');
const {firstChild } = $fruits;
console.log(firstChild); //#text
const {nextSibling} = firstChild;
console.log(nextSibling); //li.apple
const {previousSibling} = nextSibling;
console.log(previousSibling); //#text
const {firstElementChild} = $fruits;
console.log(firstElementChild); //li.apple
const {nextElementSibling} = firstElementChild;
console.log(nextElementSibling); //li.banana
const{previousElementSibling} = nextElementSibling;
console.log(previousElementSibling); //li.apple
</script>
</body>
</html>
'JavaScript' 카테고리의 다른 글
[Deep dive] 39장 DOM (3) (0) | 2023.08.21 |
---|---|
[Deep dive] 39장 DOM (2) (0) | 2023.08.21 |
[Deep dive] 38장 브라우저의 렌더링 과정 (0) | 2023.08.18 |
[Deep dive] 37장 Set과 Map (0) | 2023.08.18 |
[Deep dive] 36장 디스트럭처링 할당 (0) | 2023.08.18 |