39. DOM
- DOM (Document Object Model)
- HTML 문서의 계층적 구조와 정보를 표현함
- 이를 제어할 수 있는 프로퍼티와 메서드(API)를 제공하는 트리 자료구조
39.1. 노드
39.1.1. HTML 요소와 노드 객체
- HTML 요소: HTML 문서를 구성하는 개별적인 요소
- HTML 요소 간 중첩 관계에 의한 계층적인 부자 관계 형성
- 노드 객체: 렌더링 엔진에 의해 파싱된 HTML 요소, DOM을 구성함
- HTML 어트리뷰트 -> 어트리뷰트 노드
- HTML 텍스트 콘텐츠 -> 텍스트 노드
- HTML의 부자 관계를 반영하여 모든 노드 객체들을 트리 자료구조로 구성
트리 자료구조 (tree data structure)
- 부모 노드와 자식 노드로 구성되어, 노드 간의 계층적 구조를 표현하는 비선형 자료구조
- 루트 노드 (root node) : 하나의 최상위 노드에서부터 시작 (부모 노드 없음, 0개 이상의 자식 노드)
- 자식 노드가 없는 노드는 리프 노드(leaf node)
- DOM, DOM 트리: 노드 객체들로 구성된 트리 자료구조
39.1.2. 노드 객체의 타입
-
문서 노드 (document node)
- DOM 트리의 최상위 루트 노드 (= document 객체)
- 브라우저가 렌더링한 HTML 문서 전체를 가리키는 객체
- 전역 객체 window의 document 프로퍼티에 바인딩 됨
- 브라우저 환경의 모든 자바스크립트 코드는 하나의 전역 객체 window 공유함
- HTML 문서당 document 객체는 유일함
- DOM 트리의 노드들에 접근하기 위한 진입점 역할
-
요소 노드 (element node)
- HTML 요소를 가리키는 객체
- HTML 요소 간 중첩에 의해 부자 관계를 가지며, 정보를 구조화
- 문서의 구조를 표현함
-
어트리뷰트 노드 (attribute node)
- HTML 요소의 어트리뷰트를 가리키는 객체
- 어트리뷰트가 지정된 HTML 요소의 요소 노드와 연결됨
- 어트리뷰트를 참조하거나 변경하려면 요소 노드에 먼저 접근 후 어트리뷰트 노드에 접근 가능
-
텍스트 노드 (text node)
- HTML 요소의 텍스트를 가리키는 객체
- 문서의 정보를 표현
- 요소 노드의 자식 노드이며, 자식 노드를 가질 수 없는 리프 노드임
- DOM 트리의 최종단
-
이외에도 Comment 주석 노드, DocumentType 노드, DocumentFragment 노드 등이 있음
39.1.3. 노드 객체의 상속 구조

- DOM을 구성하는 노드 객체는 자신의 구조와 정보를 제어할 수 있는 DOM API 사용
- 부모, 형제, 자식을 탐색하거나 자신의 어트리뷰트와 텍스트 조작 가능
- 노드 객체는 브라우저 환경에서 추가적으로 제공하는 호스트 객체
- 요소 노드
- Element 인터페이스 및 HTMElement, 태그 종류별로 세분화된 인터페이스 상속 받음
- ex) input 요소 노드 객체 -> 프로토타입 체인에 있는 모든 프로토타입의 프로퍼티나 메서드 상속받아 사용 가능 (Object, EventTarget, Node, Element, HTMLElement, HTMLInputElement)
- 노드 객체의 상속 구조는 개발자 도구의 Elements > Properties 확인 가능
- 노드 객체의 종류에 따라 공통으로 갖는 기능, 또는 고유한 기능도 있음
- HTML 요소가 객체화된 요소 노드 객체는 HTML 요소의 공통 기능을 가짐 (HTMLElement 인터페이스 - style 프로퍼티 등)
- 공통된 기능일수록 프로토타입 체인 상위, 고유 기능일수록 하위에 상속 구조를 가짐
39.2. 요소 노드 취득
- 요소 노드 취득은 HTML 구조나 내용 또는 스타일 등을 동적으로 조작하기 위한 시작점
39.2.1. id를 이용한 요소 노드 취득
Document.prototype.getElementById : 인수로 전달한 id 어트리뷰트 값을 갖는 요소 노드 탐색하여 반환
- id는 HTML 문서 내에서 유일한 값이어야 하며, 단 하나의 요소 노드만 반환
- 단, 중복 id 값을 갖는 여러 개가 존재하더라도 에러는 발생하지 않음
- 첫 번째 요소 노드만 반환함
- 존재하지 않는 경우 null 반환
- id 어트리뷰트를 부여하면 동일한 이름의 전역 변수가 암묵적으로 선언됨
- 동일한 이름의 전역 변수가 이미 선언된 경우 재할당 되지 않음
<div id="foo"></div>
<script>
console.log(foo === document.getElementById('foo'));
</script>
39.2.2. 태그 이름을 이용한 요소 노드 취득
Document/Element.prototype.getElementsByTagName
- 인수로 전달한 태그 이름을 갖는 모든 요소 노드들을 탐색하여 반환
- 여러 개의 요소 노드 객체를 갖는 DOM 컬렉션 객체 HTMLCollection 객체 반환
- HTMLCollection: 유사 배열 객체이면서 이터러블
- 모든 요소 노드를 취득하려면 인수로 '*' 전달
- 존재하지 않는 경우 빈 HTMLCollection 객체 반환
- Document 메서드: DOM 전체에서 요소 노드를 탐색하여 반환
- Element 메서드: 특정 요소 노드의 자손 노도 중에서 탐색하여 반환
39.2.3. class를 이용한 요소 노드 취득
Document/Element.prototype.getElementsByClassName
- 인수로 전달한 클래스 어트리뷰트 값을 갖는 모든 요소 노드들을 탐색하여 반환
- 여러 개의 요소 노드 객체를 갖는 DOM 컬렉션 객체 HTMLCollection 객체 반환
- 공백으로 구분하여 여러 개의 class 전달 가능
- 존재하지 않는 경우 빈 HTMLCollection 객체 반환
39.2.4. CSS 선택자를 이용한 요소 노드 취득
- CSS 선택자: 스타일을 적용하고자 하는 HTML 요소를 특정할 때 사용
Document/Element.prototype.querySelector
- 인수로 전달한 CSS 선택자를 만족시키는 하나의 요소 노드 탐색하여 반환
- 여러 개인 경우 첫 번째 요소 노드만 반환
- 존재하지 않는 경우 null 반환
- 문법에 맞지 않는 경우 DOMException 에러 발생
Document/Element.prototype.querySelectorAll
- 인수로 전달한 CSS 선택자를 만족시키는 모든 요소 노드를 탐색하여 반환
- 여러 개의 요소 노드 객체를 갖는 DOM 컬렉션 객체인 NodeList 객체 반환
- 존재하지 않는 경우 빈 NodeList 객체 반환
- 문법에 맞지 않는 경우 DOMException 에러 발생
- 모든 요소 노드를 취득하려면 인수로 '*' 전달
- CSS 선택자 문법의 경우 비교적 성능 느림
- id 값이 있는 경우
getElementById 사용
- 이외에는
querySelector, querySelectorAll 사용 권장
39.2.5. 특정 요소 노드를 취득할 수 있는지 확인
Element.prototype.matches
- 인수로 전달한 CSS 선택자를 통해 특정 요소 노드를 취득할 수 있는지 확인
- 이벤트 위임 시 유용
39.2.6. HTMLCollection과 NodeList
HTMLCollection과 NodeList
- 유사 배열 객체이면서 이터러블 -> for ... of문 순회 가능
- 스프레드 문법이나 Array.from 메서드를 통해 배열로 변환하여 사용 권장
HTMLCollection
- 노드 객체의 상태 변화를 실시간으로 반영하는 live DOM 컬렉션 객체
- 실시간 상태 변경을 반영하여 요소가 제거될 수 있기 때문에 for문 사용 지양
- for 문을 역방향으로 순회하거나 while문으로 대체 가능
- 배열 고차 함수 사용 권장 (forEach, map, filter, reduce 등)
NodeList
- 대부분 노드 객체의 상태 변화를 반영하지 않는 non-live 객체
- childNodes 프로퍼티가 반환하는 NodeList 객체는 live 객체로 동작함
39.3. 노드 탐색
- DOM 트리 노드 탐색 프로퍼티
- getter만 존재하는 읽기 전용 접근자 프로퍼티

39.3.1. 공백 텍스트 노드
- 공백 텍스트 노드: HTML 요소 사이의 스페잇, 탭, 줄바꿈(개행) 등의 공백 문자(white space)
39.3.2. 자식 노드 탐색
Node.prototype.childNodes
- 모든 자식 노드를 NodeList 반환
- 텍스트 노드 포함
Node.prototype.firstChild
- 첫 번째 자식 노드 반환
- 텍스트 노드이거나 요소 노드
Node.prototype.lastChild
- 마지막 자식 노드 반환
- 텍스트 노드이거나 요소 노드
Element.prototype.children
- 자식 노드의 모든 요소 노드만 HTMLCollection 반환
- 텍스트 노드 포함하지 않음
Element.prototype.firstElementChild
Element.prototype.lastElementChild
39.3.3. 자식 노드 존재 확인
Node.prototype.hasChildNodes
- 인수로 전달한 자식 노드의 존재 여부를 boolean 반환
- 텍스트 노드를 포함하여 존재 확인함
39.3.4. 요소 노드의 텍스트 노드 탐색
- 요소 노드의 텍스트 노드는 해당 요소의 자식 노드
39.3.5. 부모 노드 탐색
Node.prototype.parentNode
39.3.6. 형제 노드 탐색
Node.prototype.previousSibling
- 자신의 이전 형제 노드 반환
- 텍스트 노드 포함
Node.prototype.nextSibling
- 자신의 다음 형제 노드 반환
- 텍스트 노드 포함
Element.prototype.previousElementSibling
- 자신의 이전 형제 노드 반환
- 텍스트 노드를 제외한 요소 노드만 반환
Element.prototype.nextElementSibling
- 자신의 다음 형제 노드 반환
- 텍스트 노드를 제외한 요소 노드만 반환
39.4. 노드 정보 취득
Node.prototype.nodeType
- 노드 객체의 종류, 타입을 나타내는 상수 반환
- Node.ELEMENT_NODE : 요소 노드 타입인 상수 1 반환
- Node.TEXT_NODE : 텍스트 노드 타입인 상수 3 반환
- NOde.DOCUMENT_NODE : 문서 노드 타입인 상수 9 반환
Node.prototype.nodeName
- 노드 이름 문자열 반환
- 요소 노드 : 태그 이름 대문자 문자열 반환
- 텍스트 노드 : 문자열 '#text' 반환
- 문서 노드 : 문자열 '#document' 반환
39.5. 요소 노드의 텍스트 조작
39.5.1. nodeValue
Node.prototype.nodeValue
- getter/setter로 참조와 할당이 모두 가능한 접근자 프로퍼티
- 참조 시 텍스트 노드의 텍스트 반환
- 문서 노드나 요소 노드 참조 시 null 반환
- 할당 시 텍스트 노드의 값 변경 가능
39.5.1. textContent
Node.prototype.textContent
- getter/setter로 참조와 할당이 모두 가능한 접근자 프로퍼티
- 모든 자손 노드의 텍스트 조작 가능
- 참조 시 콘텐츠 영역 내의 모든 텍스트 반환, HTML 마크업 무시됨
- 콘텐츠 영역에 자식 요소 노드가 없고 텍스트만 존재하는 경우 간편함
- 할당 시 모든 자식 노드가 제거되고 할당한 문자열이 텍스트로 추가됨
- HTML 마크업 파싱되지 않고 문자열 그대로 추가됨
- innerHTML 유사함, 권장하지 않음 (CSS 종속적, 느림)
39.6. DOM 조작
39.6.1. innerHTML
Element.prototype.innerHTML
- getter/setter 모두 존재하는 접근자 프로퍼티
- 콘텐츠 영역 내 모든 요소 노드의 HTML 마크업 포함하여 문자열로 반환
- 할당 시 모든 자식 노드 제거되고 HTML 파싱되어 DOM에 반영
- 크로스 사이트 스크립팅 공격 취약
- HTML5는 innerHTML 프로퍼티로 삽입된 자바스크립트 코드 실행하지 않음
- 모든 자식 노드를 제거하고 재할당하여 비효율적이고 느림
39.6.2. insertAdjacentHTML
Element.prototype.insertAdjacentHTML(position, DOMString)
- 기존 요소를 제거하지 않고 지정한 위치에 새로운 요소 삽입
- 첫 번째 인수로 전달한 위치에 두 번째 인수로 전달한 문자열을 HTML 마크업 파싱하여 DOM에 반영
- 첫 번째 인수: 위치 지정 4가지
- beforebegin
- afterbegin
- beforeend
- afterend
- 크로스 사이트 스크립팅 공격 취약
39.6.3. 노드 생성과 추가
요소 노드 생성
Document.prototype.createElement(tagName)
- 인수로 전달 받은 태그 요소 노드를 생성하여 반환
- 기존 DOM에 추가되지 않고 홀로 존재하는 상태
텍스트 노드 생성
Document.prototype.createTextNode(text)
- 인수로 전달 받은 텍스트로 텍스트 노드 생성하여 반환
- 부모 노드 없이 홀로 존재하는 상태
요소 노드의 자식 노드 및 DOM 추가
Node.prototype.appendChild(childNode)
- 인수로 전달한 노드를 호출한 노드의 마지막 자식 노드로 추가
- (BUT. 추가하려는 요소 노드에 자식 노드가 하나도 없을 때 텍스트 노드만 추가하려는 경우 textContent가 간편함)
- 기존 DOM에 새로운 요소 추가 시 DOM이 한 번 변경됨
39.6.4. 복수의 노드 생성과 추가
- 여러 개의 노드를 추가하는 경우 DOM이 여러 번 변경되는 문제 발생
- 컨테이너 요소(div)를 생성하여 자식 노드로 추가한 후 컨테이너를 DOM에 추가
- 단, 불필요한 컨테이너 요소(div)가 추가되는 부작용
- DocumentFragment
Document.prototype.createDocumentFragment
- 노드 객체의 일종
- 부모 노드 없이 기존 DOM과 별도로 존재함
- 위 컨테이너 요소와 같이 별도의 서브 DOM을 구성하기 위한 용도
- DOM 추가시 자신은 제거되고 자신의 자식 노드만 추가됨
39.6.5. 노드 삽입
마지막 노드로 추가
Node.prototype.appendChild
- 삽입 위치 지정 불가, 마지막 자식 노드로 추가
지정한 위치에 노드 삽입
Node.prototype.insertBefore(newNode, childNode)
- 첫 번째 요소 노드를 두 번째 노드 앞에 삽입
- 두 번째 노드가 호출한 노드의 자식 노드가 아니면 DOMException 발생
- 두 번째 노드가 null인 경우 마지막 자식 노드로 추가됨 (=appendChild)
39.6.6. 노드 이동
- DOM에 새로운 요소들을 추가하면 기존 노드들이 이동함
39.6.7. 노드 복사
Node.prototype.cloneNode([deep: true | false])
- 노드를 복사하여 반환함
- (기본 값, false) 얕은 복사: 노드 자신만 사본 생성 (텍스트 노드도 없음)
- (true) 깊은 복사: 모든 자손 노드를 포함한 사본 생성
39.6.8. 노드 교체
Node.prototype.replaceChild(newChild, oldChild)
- 호출한 노드의 자식 노드를 다른 노드로 교체함
- oldChild 노드는 DOMd에서 제거됨
- oldChild가 호출한 노드의 자식 노드가 아니면 DOMException 발생
39.6.9. 노드 삭제
Node.prototype.removeChild(child)
- 인수로 전달한 노드를 DOM에서 삭제함
- 전달한 노드는 호출한 노드의 자식 노드여야 함
39.7. 어트리뷰트
39.7.1. 어트리뷰트 노드와 attributes 프로퍼티
- HTML 요소는 여러 개의 어트리뷰트(속성) 가질 수 있음
- 어트리뷰트는 HTML 요소의 시작 태그에 "이름='값'" 형식으로 정의
- ex.
<input id="user" type="text" value="ungmo2">
- 모든 HTML 요소에서 사용 가능한 공통 어트리뷰트
- 글로벌 어트리뷰트(id, class, style 등)와 이벤트 핸들러 어트리뷰트 등
- 특정 HTML 요소에서 사용 가능한 어트리뷰트
- input : type, value, checked 등
- HTML 어트리뷰트당 하나의 어트리뷰트 노드 생성
- 3개의 어트리뷰트가 있으면 3개의 어트리뷰트 노드 생성
- 모든 어트리뷰트 노드의 참조는 유사 배열 객체이자 이터러블인 NamedNodeMap 객체에 담긴 attributes 프로퍼티에 저장
Element.prototype.attributes
- getter만 존재하는 읽기 전용 접근자 프로퍼티
- 요소 노드의 모든 어트리뷰트 노드의 참조가 담긴 NamedNodeMap 객체 반환
39.7.2. HTML 어트리뷰트 조작
Element.prototype.getAttribute/setAttribute
- HTML 어트리뷰트 값을 취득하거나 변경 가능
getAttribute(attributeName)
setAttribute(attributeName, attributeValue)
Element.prototype.hasAttribute(attributeName)
Element.prototype.removeAttribute(attributeName)
39.7.3. HTML 어트리뷰트 vs DOM 프로퍼티
- 요소 노드 객체는 HTML 어트리뷰트에 대응하는 DOM 프로퍼티를 가짐
- DOM 프로퍼티
- getter/setter 모두 존재하는 접근자 프로퍼티
- HTML 어트리뷰트
- HTML 요소의 초기 상태 지정
- 첫 렌더링까지 어트리뷰트 값 = 요소 노드 value = HTML 어트리뷰트 값
- 상태(state)를 가진 요소 노드가 변경되면 DOM 프로퍼티 변경됨
- 요소 노드는 초기 상태와 최신 상태 2가지 관리
- 초기 상태 = 어트리뷰트 노드
- 최신 상태 = DOM 프로퍼티
어트리뷰트 노드
- HTML 요소의 초기 상태 값 관리
getAttribute / setAttribute
DOM 프로퍼티
- 사용자의 입력에 의한 상태 변화에 반응하여 동적으로 변경되는 최신 상태 값 관리
- 모든 DOM 프로퍼티가 아닌 관계 있는 DOM 프로퍼티가 관리
- ex. input 요소에서 사용자 입력 상태 변화는 value, checked 프로퍼티가 관리
HTML 어트리뷰트와 DOM 프로퍼티의 대응 관계
- 대부분 HTML 어트리뷰트와 DOM 프로퍼티 동일한 이름으로 1:1 대응
- id 어트리뷰트 : id 프로퍼티
- value 어트리뷰트 (초기 상태) : value 프로퍼티 (최신 상태)
- class 어트리뷰트 : className, classList 프로퍼티
- for 어트리뷰트 : htmlFor 프로퍼티
- td > colspan 대응하는 프로퍼티 없음
- textContent 프로퍼티 대응하는 어트리뷰트 없음
- 어트리뷰트는 대소문자 구별하지 않음, 프로퍼티 키는 카멜 케이스
DOM 프로퍼티 값의 타입
- getAttribute 로 취득한 어트리뷰트 값은 항상 문자열
- DOM 프로퍼티는 항상 문자열은 아님
- ex. checked 프로퍼티 값은 불리언 타입
39.7.4. data 어트리뷰트와 dataset 프로퍼티
- 사용자 정의 어트리뷰트와 자바스크립트 간 데이터 교환
- data- 접두사 다음 임의 이름 붙임
HTMLElement.dataset 프로퍼티로 취득
- DOMStringMap 객체 반환
- 접두사 다음에 붙인 이름을 카멜 케이스로 변환한 프로퍼티 가짐
- 존재하지 않는 이름으로 할당하면 data 어트리뷰트 추가됨
39.8. 스타일
39.8.1. 인라인 스타일 조작
HTMLElement.prototype.style
- getter/setter 모두 존재하는 접근자 프로퍼티
- 요소 노드의 인라인 스타일을 취득하거나 변경함
- CSSStyleDeclaration 타입 객체 반환
- CSS 프로퍼티 케밥 케이스 -> CSSStyleDeclaration 카멜 케이스
39.8.2. 클래스 조작
className
Element.prototype.className
- getter/setter 모두 존재하는 접근자 프로퍼티
- HTML 요소의 class 어트리뷰트 값을 취득하거나 변경함
- 문자열로 반환하거나 할당
classList
Element.prototype.classList
- class 어트리뷰트의 정보를 담은 DOMTokenList 객체 반환
- DOMTokenList
- 유사 배열 객체이면서 이터러블
- add(...className) : 전달한 문자열을 class 어트리뷰트 값으로 추가
- remove(...className) : 전달한 문자열과 일치하는 클래스 삭제, 없으면 무시
- item(index) : 전달한 index에 해당하는 클래스 반환
- contains(className) : 전달한 문자열과 일치하는 클래스 포함 여부 확인
- replace(oldClassName, newClassName) : 첫 번째 인수 문자열을 두 번째 인수 문자열로 변경
- toggle(className[.force]) : 인수로 전달한 클래스가 존재하면 제거, 존재하지 않으면 추가
- 두 번째 인수 true/false 강제 추가 및 제거
- 이외에 forEach, entries, keys, values 등 메서드 제공
39.8.3. 요소에 적용되어 있는 CSS 스타일 참조
getComputedStyle(element[, pseudo])
- 클래스나 상속을 포함한 HTML 요소에 적용된 모든 평가된 스타일 참조
- 평가된 스타일: 링크, 임베딩, 인라인, 자바스크립트, 상속 등 모든 스타일이 조합되어 최종적으로 적용된 스타일
- 첫 번째 인수로 전달한 요소 노드에 평가된 스타일이 담긴 CSSStyleDelaration 객체 반환
- 두 번째 인수로 의사(가상) 요소 지정 문자열 전달 (:after, :before)
39.9. DOM 표준
- DOM 4개의 레벨(버전)
- DOM Level 1
- DOM Level 2
- DOM Level 3
- DOM Level 4
[출처] 모던 자바스크립트, Deep Dive