
<!DOCTYPE html>
<html>
<body>
<input type="text">
<script>
// input 요소 노드 객체를 선택
const $input = document.querySelector('input');
// input 요소 노드 객체의 프로토타입 체인
console.log(
Object.getPrototypeOf($input) === HTMLInputElement.prototype,
Object.getPrototypeOf(HTMLInputElement.prototype) === HTMLElement.prototype,
Object.getPrototypeOf(HTMLElement.prototype) === Element.prototype,
Object.getPrototypeOf(Element.prototype) === Node.prototype,
Object.getPrototypeOf(Node.prototype) === EventTarget.prototype,
Object.getPrototypeOf(EventTarget.prototype) === Object.prototype
); // 모두 true
</script>
</body>
</html>
| input 요소 노드 객체의 특성 | 프로토타입을 제공하는 객체 |
|---|---|
| 객체 | Object |
| 이벤트를 발생시키는 객체 | EventTarget |
| 트리 자료구조의 노드 객체 | Node |
| 브라우저가 렌더링할 수 있는 웹 문서의 요소(HTML, XML, SVG)를 표현하는 객체 | Element |
| 웹 문서의 요소 중에서 HTML 요소를 표현하는 객체 | HTMLElement |
| HTML 요소 중에서 input 요소를 표현하는 객체 | HTMLInputElement |
정리
- DOM은 HTML 문서의 계층적 구조와 정보를 표현하는 것은 물론 노드 객체의 종류, 즉 노드 타입에 따라 필요한 기능을 프로퍼티와 메서드의 집합인 DOM API로 제공
- 이 DOM API를 통해 HTML의 구조나 내용 또는 스타일 등을 동적으로 조작한다
<!DOCTYPE html>
<html>
<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>
!DOCTYPE html>
<html>
<body>
<ul>
<li class="fruit apple">Apple</li>
<li class="fruit banana">Banana</li>
<li class="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>
// 전체 선택자 : 모든 요소를 선택
* { ... }
// 태그 선택자 : 모든 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 { ... }
<!DOCTYPE html>
<head>
<style>
.red { color: red; }
.blue { color: blue; }
</style>
</head>
<html>
<body>
<ul id="fruits">
<li class="red">Apple</li>
<li class="red">Banana</li>
<li class="red">Orange</li>
</ul>
<script>
// class 값이 'red'인 요소 노드를 모두 탐색하여 HTMLCollection 객체에 담아 반환한다.
const $elems = document.getElementsByClassName('red');
// 이 시점에 HTMLCollection 객체에는 3개의 요소 노드가 담겨 있다.
console.log($elems); // HTMLCollection(3) [li.red, li.red, li.red]
// HTMLCollection 객체의 모든 요소의 class 값을 'blue'로 변경한다.
for (let i = 0; i < $elems.length; i++) {
$elems[i].className = 'blue';
}
// HTMLCollection 객체의 요소가 3개에서 1개로 변경되었다.
console.log($elems); // HTMLCollection(1) [li.red]
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
// childNodes 프로퍼티는 NodeList 객체(live)를 반환한다.
const { childNodes } = $fruits;
console.log(childNodes instanceof NodeList); // true
// $fruits 요소의 자식 노드는 공백 텍스트 노드(39.3.1절 "공백 텍스트 노드" 참고)를 포함해 모두 5개다.
console.log(childNodes); // NodeList(5) [text, li, text, li, text]
for (let i = 0; i < childNodes.length; i++) {
// removeChild 메서드는 $fruits 요소의 자식 노드를 DOM에서 삭제한다.
// (39.6.9절 "노드 삭제" 참고)
// removeChild 메서드가 호출될 때마다 NodeList 객체인 childNodes가 실시간으로 변경된다.
// 따라서 첫 번째, 세 번째 다섯 번째 요소만 삭제된다.
$fruits.removeChild(childNodes[i]);
}
// 예상과 다르게 $fruits 요소의 모든 자식 노드가 삭제되지 않는다.
console.log(childNodes); // NodeList(2) [li, li]
</script>
</html>
결국 노드 객체의 상태 변경과 상관없이 안전하게 DOM 컬렉션을 사용하려면 HTMLCollection이던 NodeList 객체던 간에 배열로 변환하여 사용하는 것을 권장. 고차 함수를 사용하는 장점도 따라간다.
자식 노드를 탐색하기 위해 사용하는 노드 탐색 프로퍼티
| 프로퍼티 | 설명 |
|---|---|
| Node.prototype.childNodes | childNodes 프로퍼티가 반환한 NodeList에는 요소 노드뿐만 아니라 텍스트 노드도 포함되어 있을 수 있다 |
| Element.prototype.children | children 프로퍼티가 반환한 HTMLCollection에는 텍스트 노드가 포함되지 않는다. |
| Node.prototype.firstChild | 텍스트 노드이거나 요소노드 |
| Node.prototype.lastChild | 텍스트 노드이거나 요소노드 |
| Element.prototype.firstElementChild | 요소노드만 반환 |
| Element.prototype.lastElementChild | 요소노드만 반환 |
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li class="red">Apple</li>
<li class="red">Banana</li>
<li class="red">Orange</li>
</ul>
<script>
const fruites = document.getElementById('fruits');
console.log(fruits.childNodes);
// fruits요소의 모든 자식 노드 탐색 (텍스트 노드 포함)
// NodeList(7) [text, li.apple, text, li.banana, text, li.orange, text]]
console.log(fruits.children);
// fruits 요소의 모든 자식 노드를 탐색함 (요소 노드만 포함)
// HTMLCollection [li.apple, li.banana, li.orange]
console.log(fruits.firstChild); // #text
console.log(fruits.lastChild); // #text
console.log(fruits.firstElementChild); // li.apple
console.log(fruits.lastElementChild); // li.orange
</script>
| 프로퍼티 | 설명 |
|---|---|
| Node.prototype.previousSibling | 부모 노드가 같은 형제 노드 중에서 자신의 이전 형제 노드를 탐색하여 반환. previousSibling 프로퍼티가 반환하는 형제 노드는 요소 노드 뿐만 아니라 텍스트 노드일 수도 있다 |
| Node.prototype.nextSibling | 부모 노드가 같은 형제 노드 중에서 자신의 다음 형제 노드를 탐색하여 반환. nextSibling 프로퍼티가 반환하는 형제 노드는 요소 노드 뿐만 아니라 텍스트 노드일 수도 있다 |
| Element.prototype.previousElementSibling | 부모 노드가 같은 형제 노드 중에서 자신의 이전 형제 노드를 탐색하여 반환. previousSibling 프로퍼티는 요소 노드만 반환 |
| Element.prototype.nextElementSibling | 부모 노드가 같은 형제 노드 중에서 자신의 다음 형제 노드를 탐색하여 반환. nextSibling 프로퍼티는 요소 노드만 반환 |
| 프로퍼티 | 설명 |
|---|---|
| Node.prototype.nodeType | 노드 객체의 종류. 즉, 노드 타입을 나타내는 상수를 반환. 노드 타입 상수는 Node에 정의되어 있음 Node.ELEMENT_NODE: 요소 노드 타입을 나타내는 상수 1을 반환 Node.TEXT_NODE: 텍스트 노드 타입을 나타내는 상수 3을 반환 Node.DOCUMENT_NODE: 문서 노드 타입을 나타내는 상수 9를 반환 |
| Node.prototype.nodeName | 노드의 이름을 문자열로 반환 요소 노드: 대문자 문자열로 태그 이름("UL", "LI" 등)을 반환 텍스트 노트: 문자열 "#text"를 반환 문서 노드: 문자열 "#document"를 반환 |
<!DOCTYPE html>
<html>
<body>
<div id="foo">Hello <span>world!</span></div>
</body>
<script>
// #foo 요소 노드의 모든 자식 노드가 제거되고 할당한 문자열이 텍스트로 추가된다.
// 이때 HTML 마크업이 파싱되지 않는다.
document.getElementById('foo').textContent = 'Hi <span>there!</span>'; // 이 태그는 파싱되지 않고 문자 그대로 보여진다.
</script>
</html>
<!DOCTYPE html>
<html>
<body>
<div id="foo">Hello <span>world!</span></div>
</body>
<script>
// #foo 요소의 콘텐츠 영역 내의 HTML 마크업을 문자열로 취득한다.
console.log(document.getElementById('foo').innerHTML);
// "Hello <span>world!</span>"
</script>
</html>
<!DOCTYPE html>
<html>
<body>
<div id="foo">Hello <span>world!</span></div>
</body>
<script>
// HTML 마크업이 파싱되어 요소 노드의 자식 노드로 DOM에 반영된다.
document.getElementById('foo').innerHTML = 'Hi <span>there!</span>';
</script>
</html>
<!DOCTYPE html>
<html>
<body>
<div id="foo">Hello</div>
</body>
<script>
// innerHTML 프로퍼티로 스크립트 태그를 삽입하여 자바스크립트가 실행되도록 한다.
// HTML5는 innerHTML 프로퍼티로 삽입된 script 요소 내의 자바스크립트 코드를 실행하지 않는다.
document.getElementById('foo').innerHTML
= '<script>alert(document.cookie)</script>';
</script>
</html>
<!DOCTYPE html>
<html>
<body>
<div id="foo">Hello</div>
</body>
<script>
// 에러 이벤트를 강제로 발생시켜서 자바스크립트 코드가 실행되도록 한다.
document.getElementById('foo').innerHTML
= `<img src="x">`;
</script>
</html>
HTML 새니티제이션 : 사용자로부터 입력받은 데이터에 의해 발생할 수 있는 크로스 사이트 스크립팅 공격을 예방하기 위해 잠재적 위험을 제거하는 기능을 만한다. 새니티제이션 함수를 직접 구현할 수도 있겠지만, DOMPurify 라이브러리를 사용할 것을 권장한다.
<!DOCTYPE html>
<html>
<body>
<!-- beforebegin -->
<div id="foo">
<!-- afterbegin -->
text
<!-- beforeend -->
</div>
<!-- afterend -->
</body>
<script>
const $foo = document.getElementById('foo');
$foo.insertAdjacentHTML(beforebegin, '<p>beforebegin</p>');
$foo.insertAdjacentHTML(afterbegin, '<p>afterbegin</p>');
$foo.insertAdjacentHTML(beforeend, '<p>beforeend</p>');
$foo.insertAdjacentHTML(afterend, '<p>afterend</p>');
</script>
</html>
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
</ul>
</body>
<script>
// 1. 요소 노드 생성
const $li = document.createElement('li');
// 2. 텍스트 노드 생성
const textNode = document.createTextNode('Banana');
// 3. 텍스트 노드를 $li 요소 노드의 자식 노드로 추가
$li.appendChild(textNode);
// 4. $li 요소 노드를 #freuits 요소 노드의 마지막 자식 노드로 추가
$fruits.appendChild(#li);
</script>
</html>
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
// DocumentFragment 노드 생성
const $fragment = document.createDocumentFragment();
['Apple', 'Banana', 'Orange'].forEach(text => {
// 1. 요소 노드 생성
const $li = document.createElement('li');
// 2. 텍스트 노드 생성
const $textNode = document.createTextNode('Banana');
// 3. 텍스트 노드를 $li 요소 노드의 자식 노드로 추가
$li.appendChild(textNode);
// 4. $li 요소 노드를 DocumentFragment 노드의 마지막 자식 노드로 추가
$fragment.appendChild($li);
});
// 5. DocumentFragment 노드를 #fruits 요소 노드의 마지막 자식 노드로 추가
$fruits.appendChild($fragment);
</script>
</html>
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
</ul>
</body>
<script>
// 요소 노드 생성
const $li = document.createElement('li');
// 텍스트 노드를 $li 요소 노드의 마지막 자식 노드로 추가
$li.appendChild(document.createTextNode('Orange'));
// $li 요소 노드를 #fruits 요소 노드의 마지막 자식 노드로 추가
document.getElementById('fruits').appendChild($li);
</script>
</html>
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
// 요소 노드 생성
const $li = document.createElement('li');
// 텍스트 노드를 $li 요소 노드의 마지막 자식 노드로 추가
$li.appendChild(document.createTextNode('Orange'));
// $li 요소 노드를 #fruits 요소 노드의 마지막 자식 요소 앞에 삽입
$fruits.insertBefore($li, $fruits.lastElementChild);
// Apple - Orange - Banana
</script>
</html>
...아 이건 좀..🤬
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
<li>Orange</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
// 이미 존재하는 요소 노드를 취득
const [$apple, $banana, ] = $fruits.children;
// 이미 존재하는 $apple 요소 노드를 #fruits 요소 노드의 마지막 노드로 이동
$fruits.appendChild($apple); // Banana - Orange - Apple
// 이미 존재하는 $banana 요소 노드를 #fruits 요소의 마지막 자식 노드 앞으로 이동
$fruits.insertBefore($banana, $fruits.lastElementChild);
// Orange - Banana - Apple
</script>
</html>
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
const $apple = $fruits.firstElementChild;
// $apple 요소를 얕은 복사하여 사본을 생성. 텍스트 노드가 없는 사본이 생성된다.
const $shallowClone = $apple.cloneNode();
// 사본 요소 노드에 텍스트 추가
$shallowClone.textContent = 'Banana';
// 사본 요소 노드를 #fruits 요소 노드의 마지막 노드로 추가
$fruits.appendChild($shallowClone);
// #fruits 요소를 깊은 복사하여 모든 자손 노드가 포함된 사본을 생성
const $deepClone = $fruits.cloneNode(true);
// 사본 요소 노드를 #fruits 요소 노드의 마지막 노드로 추가
$fruits.appendChild($deepClone);
</script>
</html>
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
// 기존 노드와 교체할 요소 노드를 생성
const $newChild = document.createElement('li');
$newChild.textContent = 'Banana';
// #fruits 요소 노드의 첫 번째 자식 요소 노드를 $newChild 요소 노드로 교체
$fruits.replaceChild($newChild, $fruits.firstElementChild);
</script>
</html>
<!DOCTYPE html>
<html>
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
// #fruits 요소 노드의 마지막 요소를 DOM에서 삭제
$fruits.removeChild($fruits.lastElementChild);
</script>
</html>
<input id="user" type="text" value="edLee2">
<!DOCTYPE html>
<html>
<body>
<input id="user" type="text" value="ungmo2">
<script>
const $input = document.getElementById('user');
// value 어트리뷰트 값을 취득
const inputValue = $input.getAttribute('value');
console.log(inputValue); // ungmo2
// value 어트리뷰트 값을 변경
$input.setAttribute('value', 'foo');
console.log($input.getAttribute('value')); // foo
// value 어트리뷰트의 존재 확인
if ($input.hasAttribute('value')) {
// value 어트리뷰트 삭제
$input.removeAttribute('value');
}
// value 어트리뷰트가 삭제되었다.
console.log($input.hasAttribute('value')); // false
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<input id="user" type="text" value="ungmo2">
<script>
const $input = document.getElementById('user');
// attributes 프로퍼티에 저장된 value 어트리뷰트 값
console.log($input.getAttribute('value')); // ungmo2
// 요소 노드의 value 프로퍼티에 저장된 value 어트리뷰트 값
console.log($input.value); // ungmo2
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<ul class="users">
<li id="1" data-user-id="7621" data-role="admin">Lee</li>
<li id="2" data-user-id="9524" data-role="subscriber">Kim</li>
</ul>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<ul class="users">
<li id="1" data-user-id="7621" data-role="admin">Lee</li>
<li id="2" data-user-id="9524" data-role="subscriber">Kim</li>
</ul>
<script>
const users = [...document.querySelector('.users').children];
// user-id가 '7621'인 요소 노드를 취득한다.
const user = users.find(user => user.dataset.userId === '7621');
// user-id가 '7621'인 요소 노드에서 data-role의 값을 취득한다.
console.log(user.dataset.role); // "admin"
// user-id가 '7621'인 요소 노드의 data-role 값을 변경한다.
user.dataset.role = 'subscriber';
// dataset 프로퍼티는 DOMStringMap 객체를 반환한다.
console.log(user.dataset); // DOMStringMap {userId: "7621", role: "subscriber"}
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<div style="color: red">Hello World</div>
<script>
const $div = document.querySelector('div');
// 인라인 스타일 취득
console.log($div.style); // CSSStyleDeclaration { 0: "color", ... }
// 인라인 스타일 변경
$div.style.color = 'blue';
// 인라인 스타일 추가
$div.style.width = '100px';
$div.style.height = '100px';
$div.style.backgroundColor = 'yellow';
</script>
</body>
</html>
DOMTokenList 주요 메서드
- add(... className) : 인수로 전달된 1개의 이상의 문자열을 class 어트리뷰트 값으로 추가
- remove(... className) : 인수로 전달한 1개 이상의 문자열과 일치하는 class 어트리뷰트에서 삭제 (일치 하는 클래스가 없으면 무시됨)
- item(index) : 인수로 전달한 index에 해당하는 클래스를 class 어트리뷰트에서 반환
- contains(className) : 인수로 전달한 문자열과 일치하는 클래스가 class 어트리뷰트에 포함되어 있는지 확인
- replace(oldClassName, newClassName) : 첫 번째 인수 클래스를 두번째 클래스로 변경
- toggle(className[, force]) : 인수로 전달한 문자열과 일치하는 클래스가 존재하면 제거하고 존재하지 않으면 추가
이 밖에 forEach, entires, keys, values, supports 등의 메서드를 제공