39.DOM

2024. 11. 27. 16:37기록/Modern JavaScript Deep Dive

    목차

DOM

HTML 문서의 계층적 구조와 정보를 표현하며

이를 제어할 수 있는 API인 프로퍼티와 메서드를 제공하는 트리 자료구조


노드

<div class="cat">Hello</div>
  • <div : 시작태그
  • class : 어트리뷰트 이름
  • cat : 어트리뷰트 값
  • Hello : 콘텐츠
  • </div> : 종료 태그

 

HTML 요소는 렌더링 엔진에 의해 파싱되어 DOM을 구성하는 요소 노드 객체로 변환됨

  • HTML 요소의 어트리뷰트 => 어트리뷰트 노드
  • HTML 요소의 텍스트 콘텐츠 => 텍스트 노드

 

트리 자료구조

부모 노드와 자식 노드로 구성되어

노드 간의 계층적 구조를 표현하는 비선형 자료구조

 

  • 하나의 최상위 노드에서 시작함
  • 최상위 노드는 부모 노드가 없고 루트 노드라고 함
  • 루트 노드: 0개 이상의 자식 노트를 가짐
  • 리프 노드: 자식 노드가 없는 노드

 

DOM (Document Object Model) : 노드 객체들로 구성된 트리 자료구조


주요 노드 타입

문서 노드 (Document Node)

  • 루트 노드로서 document 객체를 가리킴
  • window.document 또는 document 로 참조 가능
  • DOM 트리의 노드들에 접근하기 위한 진입점 역할
  • 요소, 어트리뷰트, 텍스트 노드에 접근하기 위해서는 문서 노드를 통해야 함

 

요소 노드 (Element Node)

  • HTML 요소를 가리키는 객체
  • 문서의 구조를 표현함

 

어트리뷰트 노드 (Attribute Node)

  • HTML 요소의 어트리뷰트를 가리키는 객체
  • 어트리뷰트가 지정된 HTML 요소의 요소 노드와 연결되어 있음
  • 요소 노드에만 연결되어 있기 때문에 부모 노드가 없으므로 요소 노드의 형제 노드는 아님
  • 어트리뷰트를 참조/변경하려면 먼저 요소 노드에 접근해야 함

 

텍스트 노드 (Text Node)

  • HTML 요소의 텍스트를 가리키는 객체
  • 문서의 정보를 표현함
  • 요소 노드의 자식 노드이며 자식 노드가 없는 리프 노드임 (DOM 트리의 최종단)
  • 텍스트 노드에 접근하려면 부모 노드인 요소 노드에 접근해야 함

노드 객체의 상속 구조

DOM을 구성하는 노드 객체는 자신의 구조/정보를 제어할 수 있는 DOM API를 사용할 수 있음

이를 통해 노드 객체는 부모/형제/자식을 탐색할 수 있고, 자신의 어트리뷰트/텍스트를 조작할 수 있음

 

노드 객체

  • ECMAScript 사양에 정의된 표준 빌트인 객체가 아니라
  • 브라우저 환경에서 추가적으로 제공하는 호스트 객체임
  • 자바스크립트 객체이기 때문에 프로토타입에 의한 상속 구조를 가짐
  • 모든 노드 객체는 Object, EventTarget, Node 인터페이스를 상속받음

 

요소 노드

  • Element 인터페이스를 상속받음
  • 추가적으로 HTMLElement 인터페이스를 상속받음
  • 태그의 종류별로 세분화된 HTMLHeadElement, HTMLBodyElement 등의 인터페이스를 상속받음

예) input 요소 노드 객체는 HTMLInputElement, HTMLElement, Element, Node, EventTarget, Object 의

프로토타입에 바인딩 되어있는 프로토타입 객체를 상속받음

프로토타입 체인에 있는 모든 프로토타입의 프로퍼티/메서드를 상속받아 사용 가능


요소 노드 취득

 

1. id 를 이용

document.getElementById('cat')
  • id 값이 'cat' 인 요소 노드를 탐색하여 반환함
  • 중복된 id 값을 가진 요소가 있을 경우 첫 번째 요소 노드만 반환함
  • 존재하지 않을 경우 null 반환

 

2. 태그 이름을 이용

document.getElementsByTagName('li');
  • 해당 태그 이름을 갖는 모든 요소 노드들을 탐색하여 반환함
  • 반환하는 DOM 컬렉션 객체인 HTMLCollection 객체는 유사 배열 객체이면서 이터러블임
  • 인수로 * 를 전달하면 HTML 문서의 모든 요소 노드 취득 가능

 

3. class 를 이용

document.getElementsByClassName('animal cat');
  • 전달한 class 값을 갖는 모든 요소 노드들을 탐색하여 반환함
  • 공백으로 구분하여 여러 개의 class 지정 가능
  • 반환하는 DOM 컬렉션 객체인 HTMLCollection 객체는 유사 배열 객체이면서 이터러블임
  • 존재하지 않을 경우 빈 HTMLCollection 객체 반환

 

4. CSS 선택자를 이용

document.querySelector('.cat');
document.querySelectorAll('.cat');
  • querySelectorAll 메서드는 전달한 선택자를 만족시키는 모든 요소 노드를 탐색하여 반환함
  • 반환하는 DOM 컬렉션 객체인 NodeList 객체는 유사 배열 객체이면서 이터러블임
  • 존재하지 않을 경우 빈 NodeList 객체 반환
  • 선택자가 문법에 맞지 않을 경우 DOMException 에러 발생
  • 인수로 * 를 전달하면 HTML 문서의 모든 요소 노드 취득 가능

 

5. 특정 요소 노드를 취득할 수 있는지 확인

const catEl = document.querySelector('.cat');

// catEl 노드는 #animal > li.cat 선택자로 취득 가능
console.log(catEl.matches('#animal > li.cat')); // true

// carEl 노드는 #animal > li.dog 선택자로 취득 불가능
console.log(catEl.matches('#animal > li.dog')); // false
  • 전달한 css 선택자를 통해 특정 요소 노드를 취득할 수 있는지 확인 가능

 

6. HTMLCollection과 NodeList

  • 유사 배열 객체이면서 이터러블임
  • 스프레드 문법이나 Array.from 메서드를 사용하여 배열로 변환 가능
  • 예상과 다르게 동작할 때가 있기 때문에 배열로 변환하여 사용하는 것이 권장됨
  • 배열로 변환하면 배열의 유용한 고차 함수 사용 가능 (forEach, map, filter, reduce 등)

노드 탐색

자식 노드 탐색

childNodes

  • 자식 노드를 모두 탐색하여 NodeList 에 담아 반환
  • 텍스트 노드 O

children

  • 자식 노드 중에서 요소 노드만 모두 탐색하여 HTMLCollection 에 담아 반환
  • 텍스트 노드 X

firstChild

  • 첫 번째 자식 노드 반환
  • 텍스트 노드 O

lastChild

  • 마지막 자식 노드 반환
  • 텍스트 노드 O

firstElementChild

  • 첫번째 자식 요소 노드 반환
  • 텍스트 노드 X

lastElementChild

  • 마지막 자식 요소 노드 반환
  • 텍스트 노드 X

hasChildNodes

  • 자식 노드 존재 여부를 반환
  • 텍스트 노드 O
  • 텍스트 노드가 아닌 요소 노드가 존재하는지 확인하려면
    children.length 또는 childElementCount 사용

 

부모 노드 탐색

parentNode

 

형제 노드 탐색

previousSibling

  • 자신의 이전 형제 노드를 탐색하여 반환함
  • 텍스트 노드 O

nextSibling

  • 자신의 다음 형제 노드를 탐색하여 반환함
  • 텍스트 노드 O

previousElementSibling

  • 자신의 이전 형제 요소 노드를 탐색하여 반환함
  • 텍스트 노드 X

nextElementSibling

  • 자신의 다음 형제 요소 노드를 탐색하여 반환함
  • 텍스트 노드 X

노드 정보 취득

nodeType

  • 노트 타입을 나타내는 상수 반환
  • Node.ELEMENT_NODE: 요소 노드 타입, 상수 1 반환
  • Node.TEXT_NODE: 텍스트 노드 타입, 상수 3 반환
  • Node.DOCUMENT_NODE: 문서 노트 타입, 상수 9 반환

nodeName

  • 노드 이름을 문자열로 반환
  • 요소 노드: 대문자 문자열로 태그 이름 반환 ("UL", "LI" 등)
  • 텍스트 노드: 문자열 "#text" 반환
  • 문서 노드: "#document" 반환

요소 노드의 텍스트 조작

nodeValue

  • 텍스트 노드의 텍스트 반환
  • 텍스트 노드가 아닌 노드의 nodeValue 프로퍼티를 참조하면 null 반환
  • 참조와 할당 모두 가능

 

textContent

  • 요소 노드의 텍스트 + 모든 자손 노드의 텍스트를 모두 취득/변경 가능
  • HTML 마크업은 무시됨
  • 요소 노드의 콘텐츠 영역에 텍스트만 존재할 경우 textContent 가 더 간단함
  • 문자열 할당 시 모든 자식 노드가 제거되고 할당한 문자열이 텍스트로 추가됨 (HTML 마크업 파싱 X)

 

innerText

  • CSS 에 순정적임 (visibility: hidden; 일 경우 해당 요소 노드의 텍스트를 반환하지 않음 등)
  • CSS 를 고려해야 하기 때문에 textContent 프로퍼티보다 느림

DOM 조작

innerHTML

  • 요소 노드의 HTML 마크업을 취득/변경 가능
  • 요소 노드의 콘텐츠 영역 내에 포함된 모든 HTML 마크업을 문자열로 반환함
  • 문자열 할당 시 모든 자식 노드가 제거되고
    할당한 문자열에 포함되어 있는 HTML 마크업이 파싱되어 자식 노드로 반영됨
  • 기존의 자식 노드를 모두 제거하고 새로 생성하기 때문에 효울적이지 않음
  • 삽입 위치를 지정할 수 없기 때문에 위치를 지정해야 할 경우 사용하지 않는 것이 좋음
  • 크로스 사이트 스크립팅 공격에 취약하기 때문에
    HTML 새니티제이션을 사용해야 함

 

HTML 새니티제이션

DOMpurify.sanitize('<img scr="x" onerror="alert(document.cookie)">');
// <img src="x">
  • 사용자로부터 입력받은 데이터에 의해 발생할 수 있는
    크로스 사이트 스크립팅 공격을 예방하기 위해 잠재적 위험을 제거하는 기능
  • DOMPurify 라이브러리 사용

 

insertAdjacentHTML

  • 기존 요소를 제거하지 않으면서 위치를 지정해 새로운 요소를 삽입함
  • 두 번째 인수로 전달한 HTML 마크업 문자열을 파싱하고
    그 결과로 생성된 노드를 첫 번째 인수로 전달한 위치에 삽입함
  • 첫 번째 인수로 전달 가능한 문자열
    • beforebegin
    • afterbegin
    • beforeend
    • afterend
  • 기존 요소에 영향을 주지 않기 때문에 innerHTML 보다 효울적이고 빠름
  • HTML 마크업 문자열을 파싱하기 때문에 크로스 사이트 스크립팅 공격에 취약함

 

노드 생성과 추가

const liEl = document.createElement('li'); // 요소 노드 생성
const textNode = document.createTextNode('cat'); // 텍스트 노드 생성

liEl.appendChild(textNode); // 텍스트 노드를 li 요소 노드의 자식 노드로 추가
liEl.textContent = 'cat'; // 요소 노드에 자식이 없을 경우 이 방법이 더 간편함

 

 

DocumentFragment

const fragment = document.createDocumnetFragment();
  • 노드 객체의 일종, 부모 노드가 없어 기존 DOM 과는 별도로 존재함
  • 별도의 서브 DOM 을 구성하여 기존 DOM 에 추가하기 위한 용도로 사용
  • 실제 DOM 변경은 한 번만 발생되기 때문에 리플로우/리페인트도 한 번만 실행됨
    (기존 DOM 에 여러 번 추가하는 것 보다 서브 DOM 을 만들어 한 번에 추가하는 것이 좋음)

 

지정한 위치에 노드 삽입

insertBefore(newNode, childNode)

  • 첫 번째 인수로 전달받은 노드를 두 번째 인수로 전달받은 노드 앞에 삽입함
  • 두 번째 인수로 전달받은 노드가 null 일 경우 appendChild 처럼 동작함 (마지막 자식 노드로 추가됨)

 

노드 이동

DOM 에 이미 존재하는 노드를 appendChild / insertBefore 을 사용하여 DOM 에 다시 추가할 경우

현재 위치에서 노드를 제거하고 새로운 위치에 노드를 추가함 (이동함)

 

노드 복사

cloneNode([deep: true | false])

  • 매개변수 deep 에 true 를 전달하면 노드를 깊은 복사하여 모든 자손 노드가 포함된 사본을 생성함
  • false 를 인수로 전달하거나 생략하면 노드를 얇은 복사하여 노드 자신만의 사본을 생성함 (텍스트 노드도 없음)

 

노드 교체

replaceChild(newChild, oldChild)

  • 첫 번째 매개변수에는 교체할 새로운 노드를 전달함
  • 두 번째 매개변수에는 이미 존재하는 교체될 노드를 전달함
  • (자신을 호출한 노드의 자식 노드인 oldChild 노드를 newChild 노드로 교체함)

 

노드 삭제

removeChild(child)

  • 매개변수로 전달한 노드를 DOM 에서 삭제함
  • 전달할 노드는 메서드를 호출한 노드의 자식 노드여야 함

어트리뷰트

<input id="user" type="text" value="cat"/>

HTML 요소는 여러 개의 어트리뷰트를 가질 수 있음

HTML 요소의 동작을 제어하기 위한 추가적인 정보를 제공함

요소 노드의 attributes 프로퍼티로 취득 가능 (요소 노드의 모든 어트리뷰트 노드의 참조가 담긴 NamedNodeMap 객체 반환)

 

글로벌/이벤트 핸들러 어트리뷰트는 모든 HTML 요소에서 공통으로 사용 가능

특정 HTML 요소에만 한정적으로 사용 가능한 어트리뷰트도 있음

(type, value, checked 어트리뷰트는 input 요소에서만 사용 가능)

  • 글로벌 어트리뷰트: id, cless, style, title, lang, tabindex, draggable, hidden 등
  • 이벤트 핸들러 어트리뷰트: onclick, onchange, onfocus, onblur, oninput, onkeypress 등

 

HTML 어트리뷰트 조작

getAttribute(attributeName)

어트리뷰트 값 참조

 

setAttribute(attributeName, attributeValue)

어트리뷰트 값 변경

 

hasAttribute(attributeName)

특정 어트리뷰트 존재 확인

 

removeAttribute(attributeName)

특정 어트리뷰트 삭제

 

HTML 어트리뷰트 vs DOM 프로퍼티

요소 노드 객체에는 HTML 어트리뷰트에 대응하는 프로퍼티가 존재함

프로퍼티들은 HTML 어트리뷰트 값을 초기값으로 가짐

 

HTML 어트리뷰트

  • HTML 요소의 초기 상태를 관리함
  • 사용자의 입력에 의해 상태가 변경되어도 변하지 않고 초기 상태를 유지함

 

DOM 프로퍼티

  • 사용자가 입력한 최신 상태를 관리함
  • 사용자의 입력에 의한 상태 변화에 반응하며 항상 최신 상태를 유지함

 

data 어트리뷰트와 dataset 프로퍼티

<div id="1" data-user-id="1234" data-role="admin">Lee</div>
...
<script>
    const user = document.querySelector('#1');
    
    console.log(user.dataset.userId); // 취득
	user.dataset.role = 'user'; // 변경
    user.dataset.animal = 'cat'; // 추가
    
    console.log(user.dataset); 
    // DOMStringMap {userId: "1234", role: "user", animal: "cat"}
    // <div id="1" data-user-id="1234" data-role="user" data-animal="cat">Lee</div>
</script>
  • HTML 요소에 정의한 사용자 정의 어트리뷰트와 자바스크립트 간 데이터 교환 가능
  • data 어트리뷰트는 data-접두사 다음에 임의의 이름을 붙여 사용함
  • data 어트리뷰트의 값은 dataset 프로퍼티로 취득 가능 (data-접두사 다음에 카멜케이스)
  • data-접두사 다음에 존재하지 않는 이름을 키로 사용해 값을 할당하면 data 어트리뷰트가 추가됨 (케밥케이스로 자동 변경됨)

스타일

<body>
    <div style="color: red">Hello World</div>
    <script>
    	const divEl = document.querySelector('div');
        console.log(divEl.style); // 인라인 스타일 취득 
        // CSSStyleDeclaration { 0: "color", ... }
        divEl.style.color = 'blue'; // 인라인 스타일 변경
        divEl.style.width = '100px'; // 인라인 스타일 추가
    </script>
</body>
  • style 프로퍼티를 참조하면 CSSStyleDeclaration 타입의 객체를 반환함
  • CSSStyleDeclaration 객체는 다양한 CSS 프로퍼티에 대응하는 프로퍼티를 가지고 있음
  • 이 프로퍼티에 값을 할당하면 해당 CSS 프로퍼티가 인라인 스타일로 HTML 요소에 추가되거나 변경됨
  • CSS 프로퍼티는 케밥케이스를 따르고 CSSStyleDeclaration 객체의 프로퍼티는 카멜케이스를 따름
  • 단위 지정이 필요한 CSS 프로퍼티의 값은 반드시 단위를 지정해야 함 (지정하지 않으면 적용 X)

 

클래스 조작

className 프로퍼티

  • 문자열을 반환하기 때문에 여러 개의 클래스를 반환하는 경우 다루기 불편함
  • class 는 예약어이기 때문에 사용이 불가능

 

classList 프로퍼티

  • class 어트리뷰트의 정보를 나타내는 컬렉션 객체인 DOMTokenList 객체를 반환함
  • DOMTokenList 객체는 유사 배열 객체이면서 이터러블이고 유용한 메서드들을 제공함

 

add(...className)

  • 인수로 전달한 1개 이상의 문자열을 class 어트리뷰트 값으로 추가함

 

remove(...className)

  • 인수로 전달한 1개 이상의 문자열과 일치하는 클래스를 class 어트리뷰트에서 삭제함
  • 일치하는 클래스가 class 어트리뷰트에 없으면 무시됨

 

item(index)

  • 인수로 전달한 index 에 해당하는 클래스를 class 어트리뷰트에서 반환함

 

contains(className)

  • 인수로 전달한 문자열과 일치하는 클래스가 class 어트리뷰트에 포함되어있는지 확인함

 

replace(oldClassName, newClassName)

  • 첫 번째 인수로 전달한 문자열을 두 번째 인수로 전달한 문자열로 변경함

 

toggle(className)

  • 인수로 전달한 문자열과 일치하는 클래스가 존재하면 제거하고, 존재하지 않으면 추가함
  • 두 번째 인수로 불리언 값으로 평가되는 조건식을 전달할 수 있음
    • 조건식의 결과가 true 이면 첫 번째 인수로 전달받은 문자열을 추가함
    • 조건식의 결과가 false 이면 첫 번째 인수로 전달받은 문자열을 제거함

 

요소에 적용되어 있는 CSS 스타일 참조

window.getComputedStyle(element[, pseudo])
  • style 프로퍼티는 인라인 스타일만 반환함
  • 모든 CSS 스타일을 참조해야 할 경우 getComputedStyle 메서드 사용
  • 첫 번째 인수로 전달한 요소 노드에 적용되어있는 평가된 스타일을 CSSStyleDeclaration 객체에 담아 반환함
  • 두 번째 인수로 :after, :before 와 같은 의사 요소를 지정하는 문자열 전달 가능