39 DOM
노드
HTML요소와 노드 객체
HTML요소는 HTML문서를 구성하는 개별적인 요소를 말한다.
HTML요소는 렌더링 엔진에 의해 파싱되어 DOM을 구성하는 요소 노드 객체로 변환된다.
이때 HTML요소의 어트리뷰트는 어트리뷰트 놓드로, HTML요소의 텍스트 콘텐츠는 텍스트 노드로 변환된다.
<div class="greet">HI</div>
요소노드 : div
어트리뷰트 노드 : class: greet
텍스트 노드 : HI
HTML요소의 콘텐츠 영역에는 텍스트 뿐 아니라 다른 HTML요소도 포함한다.
이때 HTML요소간에는 중첩에 의해 계층적인 부자관계가 형성된다.
이를 트리자료구조로 표현한다.
DOM 트리
노드 객체들로 구성된 트리 자료구조
노드 객체의 타입
노드 객체는 12개의 종류
상속구조를 가짐
문서노드(document)
DOM트리 최상위 루트노드
HTML문서당 document객체는 유일함
요소노드
HTML요소를 가리키는 객체
어트리뷰트 노드
어트리뷰트가 지정된 HTML요소의 요소노드와 연결되어있음
어트리뷰트 노드는 부모 노드와 연결되어 있지는 않음
요소노드를 통해 접근할 수 있음
텍스트노드
HTML요소의 텍스트를 가리키는 객체
요소노드의 자식노드이며, 리프노드임(DOM의 최종단)
노드 객체의 상속구조
노드 객체는 빌트인객체(JS엔진에서 제공)가 아니라 호스트객체(브라우저단에서 추가로 제공)
하지만 이 역시도 객체이므로 프로토타입에 의한 상속구조를 가짐
문서노드는 document, htmlDocument를 상속받음
어트리뷰트 노드는 Attr를 상속받음
텍스트 노드는 CharacterData를 상속받음
요소노드는 Element를 상속받으며, 태그 종류에 따라 세부적인 상속을 받음
요소 노드 객체 | 요소 노드객체 특성 | |
---|---|---|
Object | 객체 | |
EventTarget | 이벤트를 발생시키는 객체 | addEventListener, removeEventListener 등 |
Node | 트리 자료구조의 노드객체 | parentNode, childNodes, perviousSibling 등 |
Element | 브라우저가 렌더링 할 수 있는 웹 문서의 요소를 표현하는 객체 | |
HTMLElement | 웹 문서의 요소중 HTML요소를 표현하는 객체 | style 프로퍼티 등 |
개발자 도구의 Element > Properties에서 확인 가능함
상속을 통해 프로퍼티와 메서드를 제공함 ⇒ DOM API
요소 노드 획득
id를 통한 획득
Document.prototype.getElemntById
인수로 전달한 id어트리뷰트를 통해 요소노드를 탐색하여 하나를(첫번째 요소) 반환한다.
없는 경우 null을 반환한다.
document.getElementById('id');
id는 HTML 문서 내에서 유일해야하며, class와 달리 공백문자로 구분하여 여러 값을 가질 수 없다.
HTML에 id어트리뷰트를 부여하면 id값과 동일한 이름의 전역변수가 암묵적으로 선언되고, 해당 노드 객체가 할당된다.
단, 같은 이름의 전역변수가 이미 선언되어 있다면, 노드객체가 재할당되지 않는다.
태그이름을 통한 요소노드 획득
Document.prototype.getElementsByTagName
해당 태그를 가진 모든 요소노드를 HTMLCollection 객체에 담아 반환함
해당 메서드는 Element에서도 사용할 수 있다.
<html>
<body>
<ul id="fruits">
<li></li>
<li></li>
<li></li>
</ul>
<ul>
<li></li>
</ul>
<script>
const documentLi=document.getElementsByTagName('li');
const fruits=documentLi.getElementById('fruits');
</script>
class를 이용한 요소노드 취득
document.prototype.getElementsByClassName
인수로 전달한 class를 모두 탐색하여 HTMLCollection객체에 담아서 반환함
Element로도 사용가능(위와 동일)
CSS선택자를 이용한 요소 노드 획득
document.prototype.querySelector
인수로 전달한 css선택자를 만족시키는 하나의 요소 노드를 탐색하여 반환함
없는경우 null
document.prototype.querySelectorAll
인수로 전달한 css선택자를 만족시키는 모든 요소 노드를 탐색하여
NodeList객체에 담아서 반환함
없는경우 빈 NodeList를 반환함
특정 요소 노드 취득 가능 여부 확인
Element.prototype.matches는 인수로 전달한 css선택자를 통해 특정 요소노드를 취득할 수 있는지 없는지 반환함
HTMLCollection 과 NodeList
둘다 유사 배열 객체이면서 이터러블임
HTMLCollection은 살아있는 live객체
해당 리스트를 돌면서 무언가 작업을 하면 실시간으로 그 리스트가 수정됨
→ 생각대로 작동하지 않을 수 있음
NodeList는 대부분 Non-live객체
부작용 해결가능함
대부분의 경우 NonLive이기 때문
다만, childNodes 프로퍼티가 반환하는 NodeList객체는 HTML Collection객체와 같이 실시간으로 노드 객체의 상태 변경을 반영하는 live 객체임
⇒ 앵간하면 배열로 변환해서 고차함수를 통해 연산하자.
노드탐색
요소 노드를 하나 취득한후, 해당 노드의 형제노드, 부모노드를 프로퍼티를 통해 탐색할 수 있다.
노드종류 | 프로퍼티 |
---|---|
부모노드 | parentNode |
형제노드 | previousSibling, nextSibling |
자식 | firstChild, LastChild, childNodes |
모두 접근자 프로퍼티임 get만 지원하고, set은 지원하지 않음
공백텍스트 노드
HTML요소 사이의 스페이스, 탭, 개행 등 공백문자는 텍스트 노드를 생성한다.
이를 공백 텍스트 노드라 한다.
공백 텍스트 노드는 공백이 있는 곳에 들어감
자식노드 탐색
Node.prototype.childNodes
자식 노드를 모두 탐색함. 다만 해당 NodeList에는 텍스트 노드도 포함되어 있을 수 있다.
Element.prototype.children
자식노드중 요소노드만 탐색하여 HTMLCollection에 담아 반환한다.
children 프로퍼티가 반환한 HTMLCollection에는 텍스트 노드가 포함되지 않는다.
Node.prototype.firstChild
첫번째 자식노드.
텍스트 노드 또는 요소노드임
Node.prototype.lastChild
마지막 노드
텍스트 노드 또는 요소노드임
Element.prototype.firstElementChild
첫번째 자식 노드
요소노드만 반환
Element.prototype.lastElementChild
마지막 자식노드
요소노드만 반환함
자식노드 존재확인
Node.prototype.hasChildNodes를 사용함
단, 이 메서드 역시 텍스트노드를 포함하여 자식노드의 존재를 확인함
텍스트 노드가 아닌 요소 노드 여부 확인은 children.length또는 childElementCount를 사용함
형제 노드 탐색
Node.prototype.previusSibling
이전 형제노드
텍스트 노드일 수 있음
Node.prototype.nextSibling
이후 형제노드
텍스트 노드일 수 있음
Element.prototype.previousElementSibling
요소노드만 반환
Element.prototype.nextElementSibling
요소노드만 반환
노드 정보획득
Node.prototype.nodeType
노드 객체의 종류를 나타내는 상수를 반환함
- Node.ELEMENT_NODE : 노드 타입 1
- Node.TEXT_NODE : 텍스트 노드 타입 3
- Node.DOCUMENT_NODE : 문서 노드 타입 9
Node.prototype.nodeName
노드 이름을 문자열로 반환
- 요소 노드 : 태그 이름을 대문자 문자열로 반환
- 텍스트 노드 : 문자열 “#text”를 반환
- 문서노드 : 문자열 “#document”를 반환
요소노드의 텍스트 조작
nodeValue
노드 객체의 값을 반환함
텍스트 노드의 텍스트를 반환
텍스트 노드가 아니라면 null을 반환함
nodeVaue를 setter로 사용하면 해당 값이 변하게 된다.
textContent
요소노드의 콘텐츠 영역 내의 모든 텍스트를 반환한다.
이때 HTML태그 등 마크업은 무시된다.
<div id="foo">HI<span>World</span></div>
console.log(document.getElementById('foo').textContent); //HI World
textContent를 setter로 사용하면 모든 자식 노드가 제거되고 할당된 문자열이 텍스트로 추가된다.
이때 HTML마크업을 입력해도 문자열 그대로 인식된다. 즉, 파싱되지 않는다.
innerText
동일한 동작을 한다.
하지만, css에 순종적이라 css에 의해 비표시로 지정된 요소 노드의 텍스트를 반환하지 않는다.
textContent보다 느리다.
DOM 조작
새로운 노드를 생성하여 추가하거나, 기존 노드를 삭제 또는 교체하는 것
리플로우와 리페인트가 발생하게됨
innerHTML
getter, setter로 작동함
HTML마크업을 취득하거나 변경함
참조할 시에, 요소노드의 콘텐츠영역 내에 포함된 모든 마크업을 문자열로 반환한다.
새로운 값을 할당하면, 모든 자식노드가 제거되고, HTML마크업이 파시오디어 요소노드의 자식노드로 DOM에 반영된다.
HTML5는 innerHTML프로퍼티로 삽입된 script요소의 js를 실행하지 않는다.
다만, img태그를 사용하면 똑같이 작동한다.
insertAdjacentHTML
기존 요소를 제거하지 않으면서 위치를 조정해 새로운 요소를 삽입함
두번째 인수로 전달한 HTML마크업을 파싱하여 첫번째 인수에 해당하는 위치에 삽입하여 DOM에 반영함
const $foo=document.getElementById('foo');
$foo.insertAdjacentHTML('beforebegin','<div>어쩌구</div>');
첫번째 인수
beforebegin, afterbegin, beforeend, afterend
기존요소에 영향없이 새롭게 삽입될 요소만을 파싱하여 자식 요소로 추가하므로 효율적이고 빠름
이 역시 XSS에 취약함
노드 생성과 추가
노드의 생성
document.prototype.createElement(tagname)을 통해 요소 노드를 생성하여 반환한다.
const li=document.createElement('li');
텍스트 노드 생성
document.prototype.createTextNode(text)를 통해 텍스트 노드를 생성하여 반환한다.
const textNode=document.createTextNode('Banana');
자식노드로 추가
li.appendChild(textNode);
또는
li.textContent='Banana';
요소노드를 DOM에 추가
부자관계로 연결하기
document.body.appendChild(li);
여러번 DOM에 추가하기보다는 Container를 사용하기
⇒ 컨테이너에 자식요소를 모두 담은다음, 컨테이너를 부모 요소에 연결하면 리플로우와 리페인팅이 한번만 일어남으로 효율적임
하지만 불필요한 div와 같이 컨테이너도 같이 DOM에 추가된다는 문제가 발생함
DocumentFragment 노드
기존 DOM과 별도로 존재하여 자식노드를 추가해도 기존 DOM에는 어떤 변경도 발생하지 않는다.
또한, 이 node를 DOM에 추가하면 자신은 제거되고 자신의 자식노드만 DOM에 추가된다.
document.prototype.createDocumentFragment를 통해 생성함
const fragment=document.createDocumentFragment();
fragement.appendChild(document.createElement('div'));
document.body.appendChild(fragment);
⇒ body에 div하나만 추가됨
노드 삽입
마지막 노드로 추가
node.prototype.appendChild는 인수로 전달 받은 자식을 노드의 마지막 노드로 추가한다.
위치를 조정할 수는 없다.
지정한 위치에 노드 삽입
Node.prototype.insertBefore(newNode, childNode)
를 통해 첫 인수로 받은 노드를 두번째 인수로 전달받은 노드 앞에 삽입한다.
다만, 두번째 인수로 전달된 childNode는 반드시 호출한 Node의 자식이어야 한다.
노드 이동
DOM에 이미 존재하는 노드를 appendChild하거나 insertBefore하게 되면 기존 노드를 제거하고 새로운 노드를 추가한다.
즉, 노드가 이동한다.
노드 복사
Node.prototype.cloneNode
노드의 사본을 생성하여 반환한다. 매개변수 deep에 true를 전달하면 노드를 깊은 복사하여 모든 자식 노드가 포함된 사본을 생성한다. 생략하면 얕은 복사하여 자신만의 사본을 생성한다. 이러면 텍스트 노드도 존재하지 않는다.
노드 교체
Node.prototype.replaceChild(newChild, oldChild) 메서드는 자신을 호출한 노드의 자식노드를 다른노드로 교체한다.
다만, 이를 호출한 Node가 부모노드여야만 한다. oldNode는 DOM에서 제거된다.
노드삭제
Node.prototype.removeChild(child)는 child에 매개변수로 전달한 노드를 DOM에서 삭제한다.
어트리뷰트
글로벌 어트리뷰트(id, class, style, title, lang, tabindex, draggable, hidden 등)와 이벤트 핸들러 어트리뷰트는 모든 HTML에서 공통적으로 사용가능하다.
다만, type, value, checked 어트리뷰트는 input요소에서만 사용가능하다.
HTML문서 파싱시, 요소의 어트리뷰트는 어트리뷰트 노드로 변환되어 요소 노드와 연결된다.
어트리뷰트 하나당 어트리뷰트 노드 하나가 생성된다.
이 모든 어트리뷰트 노드는 유사 배열 객체이자 이터러블인 NamedNodeMap객체에 담겨서 요소노드의 attributes 프로퍼티에 저장된다.
// <input id="user" type="text" value="ungmo2"/>
const {attributes} = document.getElementById('user');
console.log(attributes.id.value); //user
console.log(attributes.type.value) // text
어트리뷰트 조작
attributes 프로퍼티는 getter만 존재하는 접근자 프로퍼티라 취득은 가능하나 변경이 불가능하다.
Element.prototype.getAttribute/setAttribute 메서드를 사용하면 요소에서 직접 값을 취득, 변경 가능하다.
getAttribute(attributeName)
setAttribute(attributeName, attributeValue)
어트리뷰트 확인
Element.prototype.hasAttribute(attributeName)
어트리뷰트 삭제
Element.prototype.removeAttribute
HTML 어트리뷰트 vs DOM프로퍼티
HTML 어트리뷰트는 HTML문서의 초기값을 기억함
DOM프로퍼티는 사용자와의 상호작용 등 변화하는 값(최신값)을 기억하여 그것을 반영함
어트리뷰트 노드
HTML 요소의 초기상태는 어트리뷰트 노드에서 관리함
어트리뷰트 노드에서 관리하는 값은 사용자 입력에 의해 상태가 변경되어도 HTML어트리뷰트로 지정된 HTML요소의 초기상태를 그대로 유지한다.
getAttrbutes/setAttributes는 HTML의 초기값이다.
DOM 프로퍼티
사용자와의 상호작용을 적용한 최신상태는 요소노드의 DOM프로퍼티가 관리한다.
DOM은 항상 최신 상태를 유지한다.
이 두가지는 분리되어 상호작용하지 않는다. 별개로 관리된다.
// <input id="user" type="text" value="lee"/>
const input=document.querySelector('input');
input.value="하이";
input.getAttribute('value') //lee
input.value //하이
id 어트리뷰트, id 프로퍼티
이는 사용자 입력과 관계없이 항상 동일한 값을 유지한다.
하나가 변하면, 다른 값도 변한다.
// <input id="user" type="text" value="lee"/>
const input=document.querySelector('input');
input.id="admin";
input.getAttribute('id') //admin
HTML어트리뷰트와 DOM 프로퍼티의 대응관계
대부분의 값은 동일하게 1:1로 대응하나 예외의 경우도 존재한다.
HTML 어트리뷰트 | DOM 프로퍼티 |
---|---|
id | id |
value(초기) | value(최신) |
class | className, classList |
for | htmlFor |
textContent | 존재하지 않음 |
대소문자 구분없음 | 카멜케이스 |
타입
HTML 어트리뷰트는 항상 문자열
DOM프로퍼티는 아닐 수도 있음
data 어트리뷰트와 dataset 프로퍼티
data 어트리뷰트와 dataset 프로퍼티를 사용하면 HTML요소에 정의한 사용자 정의 어트리뷰트와 자바스크립트 간에 데이터를 교환할 수 있음
data-접두사 이후에 임의의 이름으로 어트리뷰트로 사용가능함
이를 DOM 프로퍼티인 dataset 프로퍼티에서는 카멜케이스로 접근가능함
// <li id="1" data-user-id="1" data-role="admin">lee</li>
document.querySelector('li').dataset.userId //1
document.querySelector('li').dataset.role //admin
없는 값을 사용하는 경우, html 어트리뷰트에 자동으로 추가됨
스타일
HTMLElement.prototype.style프로퍼티는 접근자 프로퍼티로 인라인 스타일을 취득하거나 추가, 변경함
style 프로퍼티를 참조하면 CSSStyleDeclaration타입 객체를 반환함
이는 CSS프로퍼티에 대응하는 프로퍼티를 가지고 있음
값을 할당하면 프로퍼티가 인라인 형태로 HTML요소에 추가됨
CSS는 케밥케이스를 따르나 CSSStyleDeclaration은 카멜케이스를 따름(CSS in JS)
CSS형태로 값을 부여하고 싶다면 대괄호 표기법으로 표현한다.
div.style.backgroundColor="yellow";
div.style['background-color']="yellow";
클래스 조작
classList, className을 통해 DOM프로퍼티를 사용할 수 있음
//<div class="blue sky" />
document.querySelector('div').className // blue sky
document.querySelector('div'.className.replace('sky', 'red'); //sky만 red로 변경
여러개를 반환하므로 하나씩 조작하기 힘듦
classList
class어트리뷰프를 나타내는 컬렉션 객체(DOMTokenList)로 유사배열객체이면서 이터러블임
add
….classList.add(’className’)
class 추가
remove
….classList.remove(’className’);
item(index)
…classList.item(1);
해당 인덱스에 해당하는 클래스를 반환함
contains
…classList.contains(’className’);
className이 존재하는지 검사
replace(oldClassName, newClassName)
….classList.replace(’red’,’blue’);
old에서 new로 변경
toggle(className)
해당 className이 존재하는지 검사하여 있으면 제거, 없으면 추가
요소에 적용된 CSS스타일 참조
style 프로퍼티는 인라인 스타일만 반환함
기존에 적용한 스타일은 반환하지 않음
상속된 것들을 포함한 모든 스타일을 반환하기 위해서는
window.getComputedStyle(element)를 사용하면 element에 적용된 모든 스타일을 CSSStyleDeclaraton객체에 담아서 반환함
두번째 인수로 의사요소(:after, :before)를 사용할 수 있다.