-
38.3 HTML 파싱과 DOM 생성에서 살펴본 바와 같이
-
┣ 브라우저의 렌더링 엔진은 HTML 문서를 파싱하여
-
┣ 브라우저가 이해할 수 있는 자료구조인 DOM을 생성함
-
┣
DOM(Document Object Model): HTML 문서의 -
┣ 계층적 구조와 정보를 표현하며 이를 제어할 수 있는
-
┣
API, 즉 프로퍼티와 메서드를 제공하는 -
┗
트리 자료구조임
- HTML 요소(HTML element) : HTML 문서를 구성하는
- ┗ 개별적인 요소를 의미함
<div class="greeting">Hello</div>-
HTML 요소 :
-
┣ 렌더링 엔진에 의해 파싱되어
-
┣ HTML을 구성하는 요소 노드 객체로 변환됨
-
┣ 이때
HTML 요소의 어트리뷰트: -
┣ 1.
어트리뷰트 노드 -
┣ 2.
HTML 요소의 텍스트 콘텐츠 -
┗ 3.
텍스트 노드로 변환됨 -
요소 노드 :
div -
┣ 어트리뷰트 노드 :
class="greeting" -
┗ 텍스트 노드 :
"Hello" -
HTML 문서 : HTML 요소의 집합으로 이루어짐
-
┣ HTML 요소 →
중첩 관계를 가짐 -
┣ 즉 : HTML 요소의 콘텐츠 영역
-
┣ (시작 태그와 종료 태그 사이)에는
-
┗
텍스트뿐만 아니라 다른 HTML 요소도 포함 가능함 -
이때 HTML 요소 간에는 중첩 관계에 의해서
-
┣
계층적인 부자(parent-child)관계가 형성됨 -
┣ 이러한 HTML 요소 간의 부자 관계를 반영하여
-
┣ HTML 문서의 구성 요소인 HTML 요소를 객체화한
-
┗
모든 노드 객체들을 트리 자료 구조로 구성함
-
트리 자료구조(tree data structure) :
-
┣ 노드들의 계층 구조로 이루어짐
-
┣ 즉 : 트리 자료구조는
-
┣ 1.
부모 노드(parent node) -
┣ 2.
자식 노드(child node)로 구성되어 -
┣ 노드 간의 계층적 구조(부자, 형제 관계)를
-
┣
표현하는 비선형 자료구조를 말함 -
┣ 트리 자료구조 :
-
┣
트리 자료구조는 하나의 최상위 노드에서 시작함 -
┣ 최상위 노드 : 부모 노드가 없으며
-
┣
루트 노드(root node)라고 부름 -
┣ 루트 노드는 0개 이상의 자식 노드를 가짐
-
┗
자식 노드가 없는 노드 → 리프 노드(leaf node) -
노드 객체들로 구성된 트리 자료구조 :
-
┣
DOM(Document Object Model)이라고 함 -
┣ 노드 객체의 트리로 구조화되어 있기 때문에
-
┗
DOM을 → DOM 트리라고 부름
공백 텍스트 노드
HTML 요소 사이의 개행이나
┣ 공백의 경우 :
┗ 텍스트 노드가 됨
- 이처럼
DOM: - ┣ 노드 객체의 계층적인 구조로
- ┣ 구성이 됨
- ┣
노드 객체는 종류가 있고 - ┣
상속 구조를 가짐 - ┗
노드 객체 : 총 12개의 종류(노드 타입) 존재
중요한 노드 타입
-
문서 노드 :
-
┣ DOM 트리의 최상위에 존재하는
-
┣ 루트 노드로서 document 객체를 가리킴
-
┣
document 객체:브라우저가 렌더링한 -
┣ HTML 문서 전체를 가리키는 객체로서
-
┣
전역 객체(window, document) 프로퍼티가 -
┣
바인딩 되어 있음 -
┗
따라서 → 문서 노드 window.document, document로 참조 -
브라우저의 환경의 모든 JS 코드
-
┣
script태그에 분리되어 있어도 -
┣
하나의 전역 객체 window를 공유하게 됨 -
┣ 따라서
모든 JS 코드: 전역 객체 window의 -
┣
document 프로퍼티에 바인딩 되어 있는 -
┗
하나의 document 객체를 바라봄
HTML 문서당 document 객체는 유일함
- 문서 노드 :
- ┣ 즉 → document 객체는
- ┣ DOM 트리의 루트 노드이므로
- ┣
DOM 트리의 노드들에 접근하기 위한 - ┣
진입점(entry point) 역할을 함 - ┣ 즉 : 1.
요소, 2.어트리뷰트, 3.텍스트 노드 - ┗ 접근하기 위해서는 문서 노드를 통해야 함
- 요소 노드 : HTML 요소를 가리키는 객체임
- ┣
요소 노드는 HTML 요소 간의 중첩에 의해 - ┣
부자 관계를 가지며 - ┣ 이 부조가 관계를 통해 정보를 구조화함
- ┗ 따라서 →
요소 노드 : 문서의 구조를 표현함
-
어트리뷰트 노드 :
-
┣
HTML 요소의 어트리뷰트를 가리키는 객체임 -
┣ 어트리뷰트 노드는 어트리뷰트가 지정된
-
┣
HTML 요소의 요소 노드와 연결되어 있음 -
┣ 단 : 요소 노드 → 부모 노드와 연결되어 있지만
-
┣
어트리뷰트 노드는 부모 노드와 연결되어 있지 않고 -
┣
요소 노드에만 연결되어 있음 -
즉 :
어트리뷰트 노드: -
┣ 부모 노드가 없으므로
노드의 형제(sibling) 노드는 아님 -
┣ 따라서 어트리뷰트 노드에 어트리뷰트를 참조, 변경
-
┗ 하기 위해서는
먼저 요소 노드에 접근해야 함
-
텍스트 노드 : HTML 요소의 텍스트를 가리키는 객체
-
┣ 요소 노드가 문서의 구조를 표현 한다면
-
┣
텍스트 노드 → 문서의 정보를 표현함 -
┣ 텍스트 노드 : 1.
요소 노드의 자식 노드이며 -
┣ 2.
자식 노드를 가질 수 없는 리프 노드(leaf node) -
┣ 즉 : 텍스트 노드 → DOM 트리의 최종단임
-
┗ 따라서 :
텍스트 노드에 접근하려면 먼저 부모 노드인 요소 노드 접근 -
외에에도
-
┣ 1.
주석을 위한 Comment 노드 -
┣ 2.
DOCTYPE을 위한 DocumentType 노드 -
┣ 3.
복수의 노드를 추가 생성 DocumentFragment -
┗ 총 12가지 존재 이는 39.6.4 에서 학습
-
DOM : 1.
HTML 문서의 계층적 구조와 -
┣
정보를 표현하며 -
┣ 2. 이를 제어할 수 있는
API -
┣ 즉 :
프로퍼티와 메서드를 제공하는 -
┗
트리 자료구조임 -
즉 : DOM을 구성하는 노드 객체 :
-
┣ 자신의 구조와 정보를 제어할 수 있는
-
┣
DOM API를 사용할 수 있음 -
┣ 이를 통해 노드 객체는
-
┣ 1.
자신의 부모, 형제, 자식을 탐색이 가능하고 -
┗ 2.
자신의 어트리뷰트, 텍스트 조작이 가능함 -
DOM을 구성하는 노드 객체 :
-
┣ ECMAScript 사양에 정의된
-
┣
표준 빌트인 객체(standard built-in objects)가 -
┣ 아닌 브라우저 환경에서 추가적으로 제공하는
-
┣
호스트 객체(host objects) -
┣ 하지만
노드 객체도 JS 객체이기 때문에 -
┗
프로토타입에 의한 상속 구조를 가짐 -
모든 노드 객체: -
┣
Object,EventTarget,Node인터페이스를 -
┣ 상속받게됨
-
┣
문서 노드: -
┣
Document,HTMLDocument인터페이스 상속 -
┣
어트리뷰트 노드:Attr -
┗
텍스트 노드:CharacterData인터페이스를 상속 -
┣
요소 노드:Element인터페이스 상속 -
┣ 요소 노드는 추가적으로
-
┣ HTMLElement 태그의 종류벌로 세분화 된
-
┣ 1.
HTMLHtmlElement -
┣ 2.
HTMLHeadElement -
┣ 3.
HTMLBodyElement -
┣ 4.
HTMLUListElement -
┗ 등의 인터페이스를 상속받음
-
이를 프로토타입 체인 관점에서 살펴보면
-
┣ Ex) input 요소를 파싱하여 객체화한
-
┣ input 요소 노드 객체는
-
┣ 1.
HTMLInputElement -
┣ 2.
HTMLElement -
┣ 3.
Element -
┣ 4.
Node -
┣ 5.
EventTarget -
┣ 6.
Object의 prototype에 바인딩되어 있는 -
┣
프로토타입 객체를 상속받음 -
┣ 즉 : input 요소 노드 객체는 프로토타입 체인에 있는
-
┗
모든 프로토타입의 프로퍼티나 메서드를 상속 받아 사용가능
배열이 객체인 동시에 배열인 것 처럼
input 요소 객체도
┣ 다음과 같이 다양한
┣ 특성을 가지는 객체이며
┣ 이러한 특성을 나타내는
┗ 기능들을 상속받음
| input 요소 노드의 객체의 특성 | 프토토타입을 제공하는 객체 |
|---|---|
| 객체 | Object |
| 이벤트를 발생시키는 객체 | EventTarget |
| 트리 자료구조의 노드 객체 | Node |
| 브라우저가 렌더링할 수 있는 웹 문서의 요소 (HTML, XML, SVG)를 표현하는 객체 |
Element |
| 웹 무서의 요소 중에서 HTML을 표현하는 객체 | HTMLElement |
| HTML 요소 중에서 input 요소를 표현하는 객체 | HTMLInputElement |
노드 객체의 상속 구조
개발자 도구의 Elements
┗ 패널 우측의 Properties 패널 확인
-
노드 객체 : 노드 객체의 종류
-
┣ 즉 : 노드 타입에 상관없이
-
┣
모든 노드 객체가 공통으로 갖는 기능, -
┣
노드 타입에 따라 고유한 기능도 있음 -
┣ Ex) 모든 노드 객체는
-
┣ 공통적으로 이벤트를 발생시킬 수 있음
-
┣ 이벤트 관련된 기능
-
┣ 1.
EventTarget.addEventListener -
┣ 2.
EventTarget.removeEventListener -
┗ 들은 EventTarget 인터페이스가 제공함
-
또한 모든 노드 객체 :
-
┣ 트리 자료구조의 노드로서
-
┣ 공통적으로
트리 탐색 기능 -
┣ 1.
Node.parentNode -
┣ 2.
Node.childNodes -
┣ 3.
Node.previousSibling -
┣ 4.
Node.nextSibling -
┣ 이나
노드 정보 제공 기능 -
┣ 1.
Node.nodeType -
┣ 2.
Node.nodeName -
┗ 이와 같은
정보는 Node 인터페이스가 제공함 -
HTML 요소가 객체화된 요소 노드 객체는
-
┣ HTML 요소가 갖는 공통적인 기능이 있음
-
┣ Ex)
input 요소 노드 객체와 -
┣
div 요소 노드 객체: -
┣ 모두 HTML 요소의 스타일을 나타내는
-
┣
style 프로퍼티가 존재함 -
┣ 이처럼 HTML 요소가 갖는 공통적인 기능들
-
┗
HTMLElement 인터페이스가 제공함
하지만 고유한 기능도 존재
div 요소 노드 : value X
┣ input : value 필요
┣ HTMLInputElement
┗ HTMLDivElement 다른 이유
-
이처럼 노드 객체 :
-
┣
공통된 기능일수록 -
┣
프로토타입 체인의 상위 -
┣
고유한 기능일수록 -
┣
프로토타입 체인의 하위에 -
┗
프로토타입 체인을 구축함 -
지금까지 살펴본 바와 같이
-
┣ DOM : HTML 문서의 계층적 구조와
-
┣ 정보를 표현하는 것은 물론 노드 객체의 종류
-
┣ 즉 :
노드 타입에 따라 필요한 기능을 -
┣
프로퍼티와 메서드의 집합인 -
┣
DOM API(Application Programming Interface)로 -
┣ 제공하게 됨
-
┣ 이
DOM API를 통해 → HTML 구조나 내용 또는 스타일 동적으로 -
┗ 조작이 가능함
-
DOM API를 사용하기 위해
-
┣ 지금까지 살펴본 노드 객체의 상속 구조를
-
┣ 자세히 알아야 할 필요성은 없음
-
┗ 모르더라도 사용이 가능함
-
중요점 :
-
┣ DOM이 제공하는
-
┣ 1.
프로퍼티, 2.메서드를 사용하여 -
┣
노드에 접근하고 -
┣
HTML의 구조나 내용 또는 스타일 등을 -
┣
동적으로 변경하는 방법을 익히는 것 -
┣ 프런트앤드 개발자에게 HTML이란
-
┣ 단순히 태그와 어트리뷰트를 선언적으로
-
┣ 배치하여 뷰를 구성하는 것 이상의 의미를 가짐
-
┗ 즉 :
HTML을 DOM과 연관 지어 바라보야함
-
HTML의 구조나 내용 또는 스타일 등을
-
┣ 동적으로 조작하려면
-
┣ 1. 먼저 요소 노드를 취득해야 함
-
┣
텍스트 노드:요소 노드의 자식 노드 -
┣
어트리뷰트 노드: 요소 노드와 연결되어 있음 -
┣
텍스트 노드나 어트리뷰트 노드를 조작하고자 할 때도 -
┗ 마찬가지임
-
예를 들어 문서 내의 h1 요소의
-
┣ 텍스트를 변경하고 싶은 경우
-
┣ 이 경우 먼저 DOM 트리 내에 존재하는
-
┣
h1 요소 노드를 취득할 필요성이 있음 -
┣ 그리고 취득한 요소 노드의 자식 노드인
-
┗
텍스트 노드를 변경하면 해당 h1 요소의 텍스트가 변경됨
이처럼 요소 노드의 취득 :
HTML 요소를 조작하는 시점
┣ 이를 위해 DOM은
┣ 요소 노드를 취득할 수 있는
┗ 다양한 메서드를 제공함
Document.prototype.getElementById메서드 :- ┣ 인수로 전달한 id 어트리뷰트 값(이하 id 값)을
- ┣ 갖는
하나의 요소 노드를 탐색하여 반환함 - ┣ getElementById 메서드 :
Document.prototype의 - ┣ 프로퍼티임
- ┗ 따라서 →
반드시 문서 노드인 document를 통해 호출함
<script>
// id 값이 "banana"인 요소 노드를 탐색하여 반환함
// 두 번째 li 요소가 파싱되어 생성된 노드가 반환됨
const $elem = document.getElementById('banana');
// 취득한 요소 노드의 style.color 프로퍼티 값을
// 변경함
$elem.style.color = 'red';
</script>id 값:HTML 문서 내의 유일한 값- ┣ class 어트리뷰트와 달리
- ┣
공백 문자로 구분하여 여러 개의 - ┣
값을 가질 수 없음 - ┣ 단 : HTML 문서 내의
중복된 id 값을 가지는 - ┣
HTML 요소가 존재한다 하더라도 - ┗
어떠한 에러도 발생하지 않음
이러한 경우
인수로 전달된 id 값을
┣ 가지는 첫 번째 요소 노드만
┣ 반환을 진행함
┣ 즉 : 언제나 단 하나의
┗ 요소 노드를 반환함
-
만약 인수로 전달된 id 값을 가지는
-
┣
HTML 요소가 존재하지 않는 경우 -
┣ getElementById 메서드 :
-
┗
null을 반환함 -
HTML 요소에
id 어트리뷰트를 할당하면 -
┣ 1.
id 값과 동일한 이름의 전역 변수가 -
┣
암묵적으로 선언되고 -
┗ 2.
해당 노드 객체가 할당되는 부수 효과가 있음
<html>
<div id="foo"></div>
<script>
// id 값과 동일한 이름의 전역 변수가
// 암묵적으로 선언되고
// 해당 노드 객체가 할당됨
console.lg(foo === document.getElementById('foo')); // true
// 암묵적으로 생성된 전역 프로퍼티는
// 삭제되지만
// 전역 변수는 삭제 되지 않음
delete foo;
console.log(foo); // <div id="foo"></div>
</script>
</html>- 단 :
id 값과 동일한 이름의 전역 변수가 - ┣
이미 선언되어 있으면 - ┣ 이 전역 변수에
노드 객체가 재할당 - ┗
되지 않음
<html>
<div id="foo"></div>
<script>
let foo = 1;
// id 값과 동일한 이름의 전역변수가
// 이미 선언되어 있으면
// 노드 객체가 재할당 되지 않음
console.log(foo); // 1
</script>
</html>Document.prototype/Element.prototype.getElementByTagNae- ┣ 메서드 : 인수로 전달된 태그 이름을 갖는
- ┣ 모든 요소 노드들을 탐색하여 반환함
- ┣ 메서드 이름에 포함된 Elements가 복수형인 것에서 알 수 있듯
- ┣ 여러 개의 요소 노드 객체를 갖는
- ┗
DOM 컬렉션인 HTMLCollection 객체를 반환함
<html>
<body>
<ul>
<li id="apple">Apple</li>
<li id="banana">Banana</li>
<li id="orange">Orange</li>
</ul>
<script>
// 태그 이름이 li인 요소 노드들을
// 모두 탐색하여 반환함
// 탐색된 요소 노드들은 HTMLCollection
// 객체에 담겨 반호나됨
// HTMLCollection 객체 :
// 유사 배열 객체이면서 이터러블임
const $elements = document.getElementsByTagName('li');
// 취득한 모든 요소 노드의 style.color 프로퍼티 값을 변경함
// HTMLCollection 객체를 배열로 변환하여 순회하며
// color 프로퍼티 값을 변경함
[...$elements].forEach((elem) => {
elem.style.color = 'red';
});
</script>
</body>
</html>-
함수 : 하나의 값만 반환이 가능하므로
-
┣ 여러 개의 값을 반환하려면
-
┣ 배열이나 객체와 같은 자료구조에 담아서
-
┣ 반환해야 함
-
┣
getElementByTagName메서드가 반환하는 -
┣
DOM 컬렉션 객체인 HTMLCollection 객체는 -
┗
유사 배열인 객체이면서 이터러블임 -
HTML 문서의 모든 요소 노드를 취득하려면
-
┣ getElementsByTagName 메서드의 인수로
-
┗
('*')를 전달함 -
getElementsByTagName 메서드 :
-
┣
Document.prototype에 정의된 메서드와 -
┣
Element.prototype에 정의된 메서드가 있음 -
┣
Document.prototype.getElements 메서드: -
┣ DOM의 루트 노드인 문서 노드
-
┣
즉 : document를 통해 호출하여 -
┣
DOM 전체에서 요소 노드를 탐색하여 반환함 -
┣ 하지만
Element.prototype.getElementsByTagName 메서드 -
┣
특정 요소 노드를 통해 호출하여 -
┣ 특정 노드의 자식 노드 중에서
-
┗
요소 노드를 탐색하여 반환함
만약 인수로 전달된 태그 이름
가지는 요소가 존재 않하는 경우
┣ getElementsByTagName 메서드
┗ 빈 HTMLCollection 객체를 반환
Document.prototype/Element/prototype.getElementsByClassName- ┣ 메서드 : 인수로 전달한 class 어트리뷰트 값(이하 class)을 갖는
- ┣
모든 요소 노드들을 탐색하여 반환함 - ┣
인수로 전달할 class 값은 공백으로 구분하여 - ┣
여러 개의 class를 지정할 수 있음 - ┣ getElementsByTagName 메서드와 마찬가지로
- ┣ 여러 개의 요소 노드 객체를 갖는 DOM 컬렉션 객체인
- ┗
HTMLCollection 객체를 반환함
getElementsByTagName과 동일
Element, document 구분함
┣ 목록이 없을 경우
┗ 빈 HTMLCollection 객체 반환
CSS 선택자(selector):- ┣ 스타일을 적용하고자 하는
- ┗
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 에러가 발생함
-
Document.prototype/Element.prototype.querySelectorAll -
┣ 메서드 : 인수로 전달한 CSS 선택자를 만족 시키는
-
┣ 모든 요소 노드를 타맥하여 반환함
-
┣ 여러 개의 요소 노드 객체를 갖는
-
┣ DOM 컬렉션 객체인 NodeList 객체를 반환함
-
┗
NodeList 객체 : 유사 배열 객체이면서 이터러블임
인수로 전달한 CSS 선택자
문법에 맞지 않는 경우 :
┗ DOMException 에러 발생
<html>
<body>
<ul>
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</ul>
<script>
// ul 요소의 자식 요소인
// li 요소를 모두 탐색하여 반환함
const $elements = document.querySelectorAll('ul > li');
// 취득한 모든 요소 노드들은 NodeList 객체에
// 담겨서 반환됨
console.log($elements); // NodeList(3) [li.apple, li.banana, li.orange]
// 취득한 모든 요소 노드의 style.color 프로퍼티 값을 변경
// NodeList는 forEach 메서드를 제공함
$elements.forEach((elem) => {
elem.style.color = 'red';
});
</script>
</body>
</html>getElementsByTagName과 동일
Element, document 구분함
┗ 모든 선택자 선택 (*)
- CSS 선택자 문법을 사용하면
- ┣
속도가 다소 느린것으로 알려져 있음 - ┣ 하지만 1.
일관된 방식으로 요소 노드를 취하는 것과 - ┣ 2.
구체적인 조건으로 요소 노드를 취하는 것은 - ┣ 장점으로 존재함
- ┣
따라서 id 어트리뷰트가 있는 요소 노드를 취하는 경우 - ┣
getElementById 사용 하고 그 외 → - ┗
querySelector, querySelectorAll 사용 권장
-
Element.prototype.matches메서드 -
┣ 인수로 전달한 CSS 선택자를 통해
-
┗ 특정 요소 노드를 취득할 수 잇는지 확인함
-
이벤트를 위임을 사용할 때 유용함
- DOM 컬렉션 객체인
HTMLCollection과 - ┣
NodeList는 DOM API가 여러 개의 결과 값을 - ┣ 반환하기 위한 DOM 컬렉션 객체인
- ┣
모두 유사 배열객체 이면서 이터러블임 - ┣
따라서 for...of 문으로 순회가 가능하며 - ┗
스프레드 문법을 사용해서 간단히 배열로 변환 가능
getElementByTagName- ┣
getElementsByClassName - ┣ 메서드가 반환하는 HTMLCollection 객체는
- ┣
노드 객체의 상태 변화를 실시간으로 반영하는 - ┣ 살아있는 DOM 컬렉션 객체임
- ┣
따라서 HTMLCollection 객체를 살아 있는 - ┗ 객체라고 함
<html>
<body>
<ul>
<li class="red">Apple</li>
<li class="red">Banana</li>
<li class="red">Orange</li>
</ul>
<script>
// class 값이 'red'인 모든 요소 노드를 탐색하여
// HTMLCollection 객체에 담아 반환한다.
const $elements = document.getElementsByClassName('red');
console.log($elements); // NodeList(3) [li.red, li.red, li.red]
// HTMLCollection 객체의 모든 요소의 class 값을
// blue로 변경함
// NodeList는 forEach 메서드를 제공함
for (let i = 0; i < $elements.length; i++) {
$elements[i].className = 'blue';
}
// HTMLCollection 객체의 요소가 3개에서
// 1개로 변경되었음
console.log($elements); // HTMLCollection(1) [li.red]
</script>
</body>
</html>- 하지만 우리 생각대로 동작하지 않음
- 첫번째 반복
- ┣
$elem[0]은 첫 번째 li 요소임 - ┣ 이 요소는 className 프로퍼티에 의해
- ┣ class 값이 'red'에서 'blue'로 변경됨
- ┣ 이때 첫 번째 li 요소는 class 값이
- ┣ 'red'에서 'blue'로 변경되었으므로
- ┣
실시간으로 $elements에서 제거됨 - ┣ 이처럼 HTMLCollection 객체 :
- ┣
실시간으로 노드 객체의 상태 변경을 반영하는 - ┗
살아있는 live DOM 컬렉션 객체임
- 두 번째 반복
- 첫 번째 반복에서 첫 번째 li 요소
- ┣ elements에서 제거되었음
- ┣ 따라서 elements에는 두 번째 li 요소 노드만을
- ┣ 남겨 두었음
- ┣
이로써 $elements[1] : 3번째 요소임 - ┣
고로 HTMLCollection 객체에서 제거가 이루어짐 - ┣
이때 $elements.length 요소가 for문의 조건문 - ┣
i < $elements.length가 false로 평가되기 때문에 - ┗
for 문 실행이 종료됨
이 문제는 for 문을 역방향으로 순회하는
방법을 이용하여 회피가 가능함
// for문을 역방향으로 순회
for (let i = $elements.length - 1; i >= 0; i--) {
$elements[i].className = 'blue';
}- 또는 while 문을 사용하여 HTMLCollection에
- ┣
노드 객체가 남아있지 않을 때 까지 - ┗
무한 반복을 하는 경우도 존재함
// while 문으로 HTMLCollection에
// 요소가 남아 있지 않을 때 까지
// 무한 반복함
let i = 0;
while ($elements.length > i) {
$elements[i].className = 'blue';
}- 더 간단한 해결책 :
- ┣ 문제를 일으키는 HTMLCollection 객체를
- ┣ 사용하지 않는 방법
- ┣
이를 배열로 변환하면 - ┣
부작용이 없고 - ┗
배열 고차 함수를 사용이 가능함
// 유사 배열 객체이면서 이터러블인
// HTMLCollection을 배열로 변환하여 순회
[...$elements].forEach((elem) => (elem.className = 'blue'));-
HTMLCollection 객체의 부작용을 해결하기 위해
-
┗
querySelectorAll 메서드를 사용하는 방법도 존재함 -
querySelectorAll 메서드: -
┣
DOM 컬렉션 객체인 NodeList 객체를 반환함 -
┣ 이때 NodeList 객체는 실시간으로 노드 객체의
-
┗
상태를 변경하지 않는 non-live 객체임
// querySelectorAll :
// DOM 컬렉션 객체인 NodeList를 반환함
const $elems = document.querySelectorAll('red');
// NodeList 객체 :
// NodeList.prototype.forEach 메서드를 상속받아 사용 가능
$elemes.forEach((elem) => (elem.className = 'blue'));-
childNodes 프로퍼티가 반환하는 -
┣
NodeList 객체는 HTMLCollection 객체와 같이 -
┣ 실시간으로 노드 객체의 상태를 변경하는
-
┣
live 객체로 동작하므로 -
┗ 주의가 필요함
-
따라서 : 안전하게 DOM 컬렉션을 사용하려면
-
┣ 배열로 변환하여 사용하는 것을 권장함
-
┗
다양한 배열 고차 함수 사용 가능함 -
HTMLCollection과 NodeList 객체 -
┣ 모두 유사 배열이면서 이터러블임
-
┣
고로 스프레드 문법, Array.from 메서드를 사용하여 -
┗
간단하게 배열로 변환이 가능함
const { childNodes } = $fruits;
// 스프레드 문법을 사용하여
// NodeList 객체를 배열로 변환함
[...childNodes].forEach((childNode) => {
$fruits.removeChild(childNode);
});- 요소 노드를 취득한 다음
- ┣
취득한 요소 노드를 기점으로 - ┣ DOM 트리의 노드를 옮겨 다니며
- ┣ 부모, 형제, 자식 노드 등을 탐색해야
- ┗
(traversing node walking)해야 할 때가 있음
<ul id="fruits">
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</ul>-
ul#fruits요소 : -
┣ 3개의 자식 요소를 가짐
-
┣ 이때 먼저 ul#fruits 요소 노드를 취득한 다음
-
┣ 자식 노드를 모두 탐색하거나
-
┣ 자식 노드 중 하나만 탐색이 가능함
-
┣
li.banana1요소 : 1.2개의 형제 요소와 -
┣ 2.
부모 요소를 갖는다. -
┣
li.banana 요소 노드를 취득한 다음 -
┣ 형제 노드나 부모 노드를 탐색이 가능함
-
┣ 이처럼 DOM 트리 상의 노드를 탐색할 수 있도록
-
┣
Node, Element 인터페이스는 -
┗
트리 탐색 프로퍼티를 제공함 -
parentNode, previousSibling, firstChild
-
┣ 프로퍼티 : Node.prototype이 제공
-
┣ 프로퍼티 키에 Element가 포함된
-
┣ 1.
previousElementSibling -
┣ 2.
next ElementsSibling -
┣ 3.
children 프로퍼티: -
┗
Element.prototype이 제공함 -
노드 탐색 프로퍼티: -
┣
모두 접근자 프로퍼티임 -
┣ 단 : 노드 탐색 프로퍼티는
-
┣ setter 없이 getter만 존재하여
-
┣ 참조만 가능한 읽기 전용 접근자 프로퍼티임
-
┗
읽기 전용 값 할당 → 에러 없이 무시됨
-
지금까지 언급하지 않았지만 HTML 요소 사이의
-
┣
스페이스,탭,줄바꿈(개행)등의공백 -
┣
(white space)문자는 -
┣ 텍스트 노드를 생성함
-
┗ 이를 공백 텍스트 노드라고 함
-
텍스트 에디터에서 HTML 문서에
-
┣ 1. 스페이스
-
┣ 2. 탭
-
┣ 3. 엔터 키 등을 입력하면
-
┗
공백 문자가 추가됨 -
이처럼 HTML 문서의 공백 문자는
-
┣ 공백 텍스트 노드를 생성함
-
┣ 따라서
노드를 탐색할 때는 -
┣
공백 문자가 생성한 공백 텍스트 노드에 -
┣
주의를 해야함 -
┣ 다음과 같이 인위적으로 HTML 문서의
-
┣ 공백 문자를 제거하면
-
┣ 공백 텍스트 노드를 생성하지 않음
-
┗
하지만 가독성이 좋지 않으므로 권장 X
- 자식 노드를 탐색하기 위해서는
- ┗ 다음과 같은
노드 탐색 프로퍼티를 사용함
| 프로퍼티 | 설명 |
|---|---|
Node.prototype.childNodes |
자식 노드를 모두 탐색하여 DOM 컬렉션 객체인 NodeList에 담아 반환함. childNodes 프로퍼티가 반환한 NodeList에는 요소 노드뿐만 아니라 텍스트 노드도 포함되어 있을 수 있음 |
Element.prototype.children |
자식 노드 중에서 요소 노드만 모두 탐색하여 DOM 컬렉션 객체인 HTMLCollection에 담아 반환함 children 프로퍼티가 반환한 HTMLCollection에는 텍스트 노드가 포함되지 않음 |
Node.prototype.firstChild |
첫 번째 자식 노드를 반환함. firstChild 프로퍼티가 반환한 노드는 텍스트 노드이거나 요소 노드임 |
Node.prototype.lastChild |
마지막 자식 노드를 반환함. lastChild 프로퍼티가 반환한 노드는 텍스트 노드이거나 요소 노드임 |
Element.prototype.firstElement |
첫 번째 자식 요소 노드를 반환함. firstElementChild 프로퍼티는 요소 노드만 반환함 |
Element.prototype.lastElementChild |
마지막 자식 요소 노드를 반환함. lastElementChildChild 프로퍼티는 요소 노드만 반환함 |
-
자식 노드가 존재하는지 확인하려면
-
┣
Node.prototype.hasChildNodes 메서드를 이용함 -
┣ hasChildNodes 메서드 : 자식 노드가 존재하면
-
┣ true를 반환하고 아니면 false를 반환함
-
┣ 단 : childNodes 프로퍼티와 마찬가지로
-
┗
텍스트 노드를 포함하여 자식 노드의 존재를 확인함 -
자식 노드 중에 텍스트 노드가 아닌 요소 노드가
-
┣ 존재하는지 확인하려면
-
┣ hasChildNodes 메서드 대신
-
┣ 1.
children.length 또는 -
┣ 2.
Element 인터페이스의 -
┗
childElementCount 프로퍼티를 사용
요소 노드의 텍스트 노드:- ┣
요소 노드의 자식 노드임 - ┣ 따라서 → 요소 노드의 텍스트 노드 :
- ┣
firstChild 프로퍼티로 접근이 가능함 - ┣ firstChild 프로퍼티는 첫 번째 자식 노드를 반환함
- ┣ firstChild 프로퍼티가 반환한 노드는
- ┗
텍스트 노드이거나 요소 노드임
<body>
<div id="foo">Hello</div>
<script>
// 요소 노드의 텍스트 노드는
// firstChild 프로퍼티로 접근이 가능함
console.log(document.getElementById('foo').firstChild); // #text
</script>
</body>- 부모 노드를 탐색하려면
- ┣
Node.prototype.parentNode프로퍼티를 사용함 - ┣
텍스트 노드: - ┣
DOM 트리의 최종단 노드인 leaf Node 이므로 - ┗
부모 노드가 텍스트 노드인 경우는 없음
- 부모 노드가 같은 형제 노드를 탐색하려면
- ┣ 다음과 같은
노드 탐색 프로퍼티를 사용함 - ┣ 단 :
어트리뷰트 노드는 요소 노드와 연결되어 있지만 - ┣ 부모 노드가 같은 형제 노드가 아니기 때문에
- ┣
반환되지 않음 - ┗ 즉 :
아래 프로퍼티는 텍스트 노드 또는 요소 노드만 반환함
| 프로퍼티 | 설명 |
| Node.property.previousSibling | 부모 노드가 같은 형제 노드 중에서 자신의 이전 형제 노드를
탐색하여 반환함. previousSibling 프로퍼티가 반환하는 형제
노드는 요소 노드 뿐만 아니라 텍스트 노드일 수도 있음 |
| Node.property.nextSibling | 부모 노드가 같은 형제 노드 중에서 자신의 다음 형제 노드를
탐색하여 반환함. previousSibling 프로퍼티가 반환하는 형제
노드는 요소 노드 뿐만 아니라 텍스트 노드일 수도 있음 |
| Element.property.nextElementSibling | 부모 노드가 같은 형제 노드 중에서 자신의 이전 형제 노드를
탐색하여 반환함. nextElementSibling 프로퍼티가 반환하는 형제
프로퍼티는 요소 노드만 반환함 |
| Element.property.nextElementSibling | 부모 노드가 같은 형제 노드 중에서 자신의 다음 형제 노드를
탐색하여 반환함. previousElementSibling 프로퍼티가 반환하는 형제
프로퍼티는 요소 노드만 반환함 |
- 노드 객체에 대한 정보를 취득하려면
- ┗
다음과 같은 노드 정보 프로퍼티를 사용함
| 프로퍼티 | 설명 |
|---|---|
| Node.prototype.nodeType | 노드 객체의 종류, 즉 : 노드 타입을 나타내는 상수를 반환함 노드 타입 상수 : Node에 정의되어 있음 |
| Node.ELEMENT_NODE: 요소 노드 타입을 나타내는 상수 1을 반환 | |
| Node.TEXT_NODE: 텍스트 노드 타입을 나타내는 상수 3을 반환 | |
| Node.DOCUMENT_NODE:문서 노드 타입을 나타내는 상수 9을 반환 | |
| Node.prototype.nodeName | 노드의 이름을 문자열로 반환함 |
| 요소 노드 : 대문자 문자열로 태그 이름("UL, LI")반환 | |
| 텍스트 노드 : 문자열 "#text"를 반환 | |
| 문서 노드 : 문자열 "#document"를 반환 |
-
지금까지 살펴본 노드 탐색, 노드 정보 프로퍼티 :
-
┣ 모두 읽기 전용 접근자 프로퍼티임
-
┣ 지금부터 살펴볼
Node.prototype.nodeValue 프로퍼티 -
┣ 모두 setter, getter 모두 존재하는
-
┣
접근자 프로퍼티임 -
┗
따라서 nodeValue 프로퍼티는 참조와 할당이 모두 가능함 -
노드 객체의 nodeValue 프로퍼티를
-
┣ 참조하면 노드 객체의 값을 반환함
-
┣ 노드 객체의 값이란 :
-
┣
텍스트 노드의 텍스트임 -
┣ 따라서 텍스트 노드가 아닌 노드,
-
┣ 즉 :
문서 노드나 요소 노드의 -
┣
nodeValue 프로퍼티를 참조하면 -
┗
null을 반환함
<html>
<body>
<div id="foo">Hello</div>
</body>
<script>
// 문서 노드의 nodeValue 프로퍼티를 참조함
console.log(document.nodeValue); // null
// 요소 노드의 nodeValue 프로퍼티를 참조함
const $foo = document.getElementById('foo');
console.log($foo.nodeValue); // null
// 텍스트 노드의 nodeValue 프로퍼티를 참조함
const $textNode = $foo.firstChild;
console.log($textNode.nodeValue); // Hello
</script>
</html>- 텍스트 노드의 nodeValue 프로퍼티에
- ┣ 값을 할당하면 텍스트 노드의 값
- ┣ 즉 : 텍스트를 변경할 수 있음
- ┣
따라서 → 요소 노드의 텍스트를 변경하라면 - ┗
다음과 같은 순서의 처리가 필요함
- 텍스트를 변경할 요소 노드를 취득한 다음
- ┣ 취득한 요소 노드의 텍스트 노드를 탐색함
- ┣
텍스트 노드는 요소 노드의 자식 노드 이므로 - ┗
firstChild 프로퍼티를 사용하여 탐색함
- 탐색한 텍스트 노드의
nodeValue 프로퍼티를 사용하여
- ┗
텍스트 노드의 값을 변경함
$textNode.nodeValue = 'world';-
Node.prototype.textContent프로퍼티 : -
┣ setter, getter 모두 존재하는
-
┣
접근자 프로퍼티로서 -
┣ 요소 노드의 텍스트와 모든 자손 노드의
-
┗
텍스트를 모두 취득하거나 변경함 -
요소 노드의 textContent 프로퍼티를 참조하면
-
┣
요소 노드의 콘텐츠 영역(시작 태그와 종료 태그 사이) -
┣ 내의
텍스트를 모두 반환함 -
┣ 다시 말해 요소 노드의
-
┣
childNodes 프로퍼티가 반환한 -
┣ 모든 노드들의 텍스트의 값
-
┣ 즉 :
텍스트를 모두 반환함 -
┗
이때 HTML 마크업은 무시됨
<body>
<div id="foo">Hello<span>world</span></div>
</body>- 앞서 살펴본
nodeValue 프로퍼티를 참조하여도 - ┣
텍스트를 취득이 가능함 - ┣ 단 : 텍스트 노드가 아닌 노드의 nodeValue 프로퍼티는
- ┣ null을 반환하므로 의미가 없고
- ┣ 텍스트 노드만 의미가 존재함
- ┗
코드가 더 복잡함
<body>
<div id="foo">Hello<span>world</span></div>
</body>
<script>
// #foo 요소 노드는 텍스트 노드가 아님
console.log(document.getElementById('foo').nodeValue); // null
// #foo 요소 노드의 자식 노드인 텍스트 노드의 값을 취득함
console.log(document.getElementById('foo').firstChild.nodeValue); // Hello
// #foo 요소 노드dml 자식 노드인 텍스트 노드의 값을 취득함
console.log(document.getElementById('foo').lastChild.firstChild.nodeValue); // world!
</script>-
만약 요소 노드의 콘텐츠 영역에
-
┣ 자식 요소 노드가 없고
-
┣ 텍스트만 존재한다면
-
┣
firstChild.nodeValue와 textContent 프로퍼티 -
┣ 같은 결과를 반환하게 됨
-
┗
이 경우 textContent 사용 하는 편이 코드가 간단함 -
요소 노드의 textContent 프로퍼티에 -
┣
문자열을 할당하면→ 요소 노드의 -
┣ 1.
모든 자식 노드가 제거되고 -
┣ 2.
할당한 문자열 텍스트로 추가됨 -
┣ 이때 할당한 문자열에 HTML 마크업이
-
┣ 포함되어 있어도 문자열 그대로 인식됨
-
┗
즉 : HTML 마크업이 파싱되지 않음
<body>
<div id="foo">Hello<span>world</span></div>
</body>
<script>
// #foo 요소 노드의 모든 자식 노드가 제거되고
// 할당한 문자열이 텍스트로 추가됨
// 이때 HTML 마크업이 파싱되지 않음
document.getElementById('foo').textContent = 'Hi!<span>there!</span>';
</script>- 참고 :
- ┣ textContent와 유사 동작을 하는
- ┣ innerText 프로퍼티가 존재함
- ┗
innerText 프로퍼티 사용 안하는게 좋음
- innerText 프로퍼티 : CSS에 순종적
- ┣
CSS에 비표시(visibility: hidden;) - ┗ 로 지정된 요소 노드의 텍스트를 반환하지 않음
- innerText 프로퍼티 :
- ┗
CSS 고려하므로 textContent 프로퍼티보다 느림
DOM 조작(DOM manipulation):- ┣ 새로운 노드를 생성하여 DOM에 추가하거나
- ┣
기존 노드를 삭제 또는 교체하는 것을 말함 - ┣ DOM 조작에 의해 DOM에 새로운 노드가
- ┣ 추가되거나 삭제되면
- ┣ 1.
리플로우, 2.리페인트가 발생함 - ┣ → 성능에 영향을 줌
- ┗
복잡한 콘텐츠를 다루는 DOM 조작은 주의해야 함
Element.prototype.innerHTML프로퍼티 :- ┣
setter, getter가 모두 존재하는 - ┣
접근자 프로퍼티로서 - ┣ 요소 노드의 HTML 마크업을 취득하거나
- ┣ 변경을 진행함
- ┣ 요소 노드의 innerHTML 프로퍼티 참조 →
- ┣ 요소 노드의 콘텐츠 영역(시작 태그 종료 태그 사이)
- ┗
내에 포함된 모든 HTML 마크업을 문자열로 반환함
앞의 textContent
HTML 마크업 무시
┣ innerHTML :
┗ HTML 마크업이 포함된 문자열
<html>
<body>
<div id="foo">Hello <span>world!</span></div>
</body>
<script>
console.log(document.getElementById('foo').innerHTML);
// "Hello <span>world!</span>"
</script>
</html>-
요소 노드의
innerHTML 프로퍼티에 -
┣
문자열을 할당하면 -
┣ 요소 노드의 모든 자식 노드가 제거되고
-
┣ 할당한 문자열에 포함되어 있는
-
┣
HTML 마크업이 파싱되어 -
┗
요소 노드의 자식 노드로 DOM에 반영됨 -
innerHTML 프로퍼티를 사용하면
-
┗
HTML 마크업 문자열로 DOM 조작이 가능함
<body>
<ul id="fruits">
<li class="apple">Apple</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
$fruits.innerHTML += '<li class="banana">Banana</li>';
// 노드 교체
$fruits.innerHTML = '<li class="banana">Orange</li>';
// 노드 삭제
$fruits.innerHTML = '';
</script>사용자가 입력받은 데이터(unTrusted input data)- ┣ 그대로 innerHTML 프로퍼티에 할당하는 것
- ┣
크로스 사이트 스크립팅 공격(XSS: Cross-Site Scripting Attacks)에 - ┣ 취약하기 때문에 위험함
- ┣
HTML5: - ┣ 프로퍼티로 삽입된 script 요소 내의
- ┣ JS 코드를 실행하지 않음
- ┣ 따라서
HTML5를 지원하는 브라우저에서 - ┣ JS 코드를 실행하는 것은 동작하지 않음
- ┣
하지만 script 요소 없이도 크로스 사이트 스크립팅 공격 - ┗
가능함
<script>
// 에러 이벤트를 강제로 발생시켜서
// 자바스크립트 코드가 실행되도록 함
document.getElementById('foo').innerHTML =
'<img src="x" onerror="alert(document.cookie)">';
</script>- 이처럼 innerHTML 프로퍼티를 사용한 DOM 조작은
- ┣ 구현이 간단하고 직관적이라는 장점이 있지만
- ┗
크로스 사이트 스크립팅 공격에 취약한 단점이 존재함
HTML 새니티제이션(Sanitization)
사용자로부터 입력받은
┣ 데이터에 의해 발생할 수 있는
┣ 크로스 사이트 스크립팅 공격을
┣ 예방하기 위해 잠재적 위험을
┣ 제거하는 것을 의미함
┣ 직접 사용하는 것 보다
┗ DOMPurify 라이브러리 사용 권장
DOMPurify.sanitize('<img src="x" onerror="alert(document.cooke)">');
// → <img src="x">-
innerHTML의 또 다른 단점 :
-
┣ 요소 노드의 innerHTML 프로퍼티에
-
┣
HTML 마크업 문자열을 할당하는 경우: -
┣ 요소 노드의 1.
모든 자식 노드를 제거하고 -
┣ 할당한 HTML 마크업 문자열을 파싱하여
-
┗ 2.
DOM을 변경한다는 것임 -
'+='을 사용하면
-
┣ 모든 자식 노드를 제거하고
-
┗
새롭게 요소 노드를 생성하여 자식 요소로 추가함
$fruits.innerHTML += '<li class="banana">Banana</li>';- 위 코드는 아래 코드의 축약 표현임
$fruits.innerHTML = $fruits.innerHTML + '<li class="banana">Banana</li>';-
이처럼 innerHTML 프로퍼티에
-
┣ HTML 마크업 문자열 할당하면
-
┣ 유지되어도 좋은 기존 자식 노드까지
-
┣ 모두 제거하고 새롭게 생성하는 방식
-
┗
이는 효율적이지 못함 -
innerHTML의 또 다른 단점 :
-
┣ 새로운 요소를 삽입할 때
-
┗
삽입될 위치를 지정불가함
innerHTML 단점
1. 크로스 사이트 스크립팅
2. 모두 제거 → 새로 생성하여 추가
3. 삽입 위치 지정 불가
-
Element.prototype.insertAdjacentHTML(position, DOMString) -
┣ 메서드 : 기존 요소를 제거하지 않으면서
-
┗
위치를 지정해 새로운 요소를 삽입함 -
insertAdjacentHTML 메서드 :
-
┣ 두 번째 인수로 전달한
-
┣ HTML 마크업 문자열(DOMString)을 파싱하고
-
┣ 그 결과로 생성된 노드를
-
┣ 첫 번째 인수로
전달한 위치(position)에 -
┗ 삽입하여
DOM에 반영하게 됨
첫 번째 인수 전달 가능 문자열
1. beforebegin
2. afterbegin
3. beforeend
4. aftereend
/beforebegin/
<div id="foo">/afterbegin/text/beforeeend/</div>
/afterend/-
기존 요소에 영향을 주지않고
-
┣ 새롭게 삽입될 요소만 파싱하여
-
┣ 자식 요소로 추가하므로
-
┣ innerHTML 보다
-
┗
효율적이고 빠름 -
단 : 크로스 사이트 스크립팅 공격 취약 동일
- 지금까지 살펴본
innerHTML프로퍼티와 - ┣
insertAdjacentHTML메서드: - ┣ HTML 마크업 문자열을 파싱하여
- ┣ 1.
노드를 생성하고 - ┣ 2.
DOM에 반영함 - ┣ DOM : 노드를 직접
- ┗
생성/삽입/삭제/치환메서드 제공
Document.prototype.createElement(tagName)메서드 :- ┣ 요소 노드를 생성하여 반환함
- ┣
createElement 메서드 매개변수 - ┣ tagName : 태그 이름을 나타내는 문자열을
- ┗ 인수로 전달함
// 1. 요소 노드 생성
const $li = document.createElement('li');-
createElement 메서드로 생성한 -
┣ 요소 노드 :
-
┣ 기존 DOM에 추가되는 것이 아닌
-
┣ 홀로 존재하는 상태임
-
┣
이후 생성된 요소 노드를 DOM에 -
┗
추가하는 처리가 필요함 -
c
reateElement 메서드로 생성한 요소 노드: -
┣ 아무런 자식 노드도 가지고 있지 않음
-
┣ 따라서 : 요소 노드의 자식 노드인
-
┗
텍스트 노드도 없는 상태
// 1. 요소 노드 생성
const $li = document.createElement('li');
// 생성된 요소 노드는 아무런 자식 노드가 없음
console.log($li.childNodes); // NodeList []Document.prototype.createTextNode(text)메서드:- ┣ 텍스트 노드를 생성하여 반환함
- ┣
text: 텍스트 노드의 값으로 사용한 - ┗
문자열을 인수로 전달함
// 2. 텍스트 노드 생성
const textNode = document.createTextNode('Banana');- 텍스트 노드 :
- ┣ 요소 노드의 자식 노드임
- ┣
createTextNode 메서드로 생성한 텍스트 노드 - ┣ 홀로 존재하는 상태임
- ┗
추후 처리가 필요함
-
Node.prototype.appendChild(childNode)메서드 : -
┣ 매개변수 childNode에게 :
-
┣ 인수로 전달한 노드를
appendChild 메서드를 호출한 -
┗
노드의 마지막 자식 노드로 추가함 -
appendChild 메서드의 인수로 -
┣ createTextNode 메서드로 생성한
-
┣
텍스트 노드를 전달하면 -
┣ appendChild 메서드를 호출한
-
┣
노드의 마지막 자식 노드로 -
┗
텍스트 노드가 추가됨
// 3. 텍스트 노드를 $li 요소 노드의 자식 노드로 추가
$li.appendChild(textNode);-
여기까지는 appendChild 메서드를 통해
-
┣ 요소 노드와 텍스트 노드가 분자 관계로
-
┣ 연결이 되어 있긴 하지만
-
┗
아직 기존 DOM에 추가되지는 않은 상태임 -
위 예제처럼 요소 노드에 자식 노드가 하나도
-
┣ 없는 경우에는
-
┗
textContent 프로퍼티를 사용하는 편이 더욱 간편
// 요소 노드에 자식 노드가 하나도 없는 경우
// 이렇게 하는 편이 더 간편함
$li.textContent = 'Banana';- 단 : 요소 노드에
- ┣ 자식 노드가 있는 경우
- ┣ textContent 프로퍼티를 사용할 경우
- ┣
요소 노드의 모든 자식 노드가 제거 되므로 - ┗
주의가 필요함
Node.prototype.appendChild메서드를 사용하여- ┣ 텍스트 노드와 부자 관계로 연결한 노드를
- ┣
#fruits 요소 노드의 마지막 자식 요소로 - ┗ 추가하면 됨
// 4. $li 요소 노드를 #fruits 요소 노드의
// 마지막 자식 노드로 추가
$fruits.appendChild($li);- 이 과정에서 비로소 새롭게 생성한
- ┣ 요소 노드가 DOM에 추가됨
- ┣
기존의 DOM에 요소 노드를 추가하는 - ┣
처리는 이 과정뿐임 - ┣ 위 예제는 단 하나의 요소 노드를 생성, 추가
- ┣ DOM은 한 번 변경됨
- ┗
이때 리플로우, 리페인트 실행됨
-
기존 DOM에 요소 노드를 반복하여 추가하는 것은
-
┗ 비효율적임
-
이처럼 DOM을 여러 번 변경하는 문제를
-
┣ 회피하기 위해 컨테이너 요소를 사용
-
┣ 1.
컨테이너 요소를 미리 생성한 다음 -
┣ 2. DOM에 추가해야 할 노드를
-
┣
컨테이너 요소에 자식 노드로 추가 -
┣ 3. 컨테이너 요소를 #fruits 요소에
-
┗
자식 노드로 추가하면 한 번만 변경됨
<script>
const $fruits = document.getElementById('fruits');
// 컨테이너 요소 노드 생성
const $container = document.createElement('div');
['Apple', 'Banana', 'Orange'].forEach((text) => {
// 1. 요소 노드 생성
const $li = document.createElement('li');
// 2. 텍스트 노드 생성
const textNode = document.createTextNode(text);
// 3. 텍스트 노드를 $li 요소 노드의 자식 노드로 추가
$li.appendChild(textNode);
// 4. $li 요소 노드를 컨테이너 요소의
// 마지막 자식 노드로 추가
$container.appendChild($li);
});
// 5. 컨테이너 요소 노드를 #fruits 요소 노드의
// 마지막 자식 노드로 추가
$fruits.appendChild($container);
</script>-
위 예는 DOM을 한 번만 변경하므로
-
┣ 성능에 유리하기 하지만
-
┣
불필요한 컨테이너 요소(div)가 -
┣
DOM에 추가되는 부작용이 존재함 -
┗ 이는 바람직하지 않음
-
이러한 문제는
DocumentFragment를 통해 -
┣ 해결이 가능함
-
┣ DocumentFragment :
문서,요소,어트리뷰트, -
┣
텍스트 노드와 같은노드 객체의 일종으로 -
┣
부모 노드가 없어서 기존 DOM과는 별도로 존재한다는 -
┗ 특징이 존재함
-
자식 노드들의 부모 노드로서
-
┣
별도의 서브 DOM을 구성하여 -
┗
기존 DOM에 추가하기 위한 용도로 사용함 -
DocumentFragment 노드 : 기존 DOM과는 별도로
-
┣ 존재하므로 DocumentFragment 노드에
-
┣
자식 노드를 추가하여도 기존 DOM에는 -
┣
어떤 변경도 발생하지 않음 -
┣ 또한 DocumentFragment 노드를
-
┣ DOM에 추가하면 자신은 제거되고
-
┗
자신의 자식 노드만 DOM에 추가됨 -
Document.prototype.createDocumentFragment메서드: -
┗ 비어 있는
DocumentFragment노드를 생성하여 반환함
<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(text);
// 3. 텍스트 노드를 $li 요소 노드의 자식 노드로 추가
$li.appendChild(textNode);
// 4. $li 요소 노드를 DocumentFragment 노드의
// 마지막 자식 노드로 추가
$fragment.appendChild($li);
});
// 5. DocumentFragment 노드를 #fruits 요소 노드의
// 마지막 자식 노드로 추가
$fruits.appendChild($fragment);
</script>- 따라서 : 여러 개의 요소 노드를
- ┣ DOM에 추가하는 경우
- ┣
DocumentFragment 노드를 사용하는 것이 - ┗ 더 효율적임
Node.prototype.appendChild메서드 :- ┣ 인수로 전달받은 노드를 자신을 호출한
- ┣ 마지막 자식 노드로 DOM에 추가함
- ┣ 이때 추가할 위치 지정 불가하고
- ┗
언제나 마지막 자식 노드로 추가함
<script>
const $li = document.createElement('li');
// 텍스트 노드를 $li 요소 노드의 마지막 자식 노드로 추가함
$li.appendChild(document.createTextNode('Orange'));
// $li 요소 노드를 #fruits 요소 노드의
// 마지막 자식 노드로 추가함
document.getElementById('#fruits').appendChild($li);
</script>Node.prototype.insertBefore(newNode, childNode)- ┣ 첫 번째
인수로 전달받은 노드를 - ┗ 두 번째
인수로 전달받은 노드 앞에 삽입함
<script>
const $li = document.createElement('li');
// 텍스트 노드를 $li 요소 노드의 마지막 자식 노드로 추가함
$li.appendChild(document.createTextNode('Orange'));
// $li 요소 노드를 #fruits 요소 노드의
// 마지막 자식 노드 앞에 추가함
document
.getElementById('#fruits')
.insertBefore($li, $fruits.lastElementChild);
// Apple
// Orange
// Banana
</script>-
두 번째 인수로 전달받은 노드는
-
┣ 반드시 insertBefore 메서드를 호출한
-
┣ 노드의 자식 노드이어야 함
-
┗
그렇지 않으면 DOMException 에러가 발생함 -
두 번째 인수로 전달받은 노드가
-
┣ null인 경우
-
┣ 첫 번째 인수로 전달받은 노드를
-
┣
insertBefore 메서드를 호출한 -
┣
노드의 마지막 자식 노드로 추가됨 -
┗
즉 : appendChild 메서드처럼 동작함
- DOM에 이미 존재하는 노드를
- ┣
appendChild 또는 insertBefore 메서드를 사용하여 - ┣ DOM에 다시 추가하면
- ┣
현재 위치에서 노드를 제거하고 - ┣
새로운 위치에 노드를 추가함 - ┗ 즉 : 노드가 이동함
<script>
const $fruits = document.getElementById('fruits');
// 이미 존재하는 요소 노드를 취득
const [$apple, $banana] = $fruits.children;
// 이미 존재하는 요소 노드를 #fruits
// 요소 노드의 마지막 노드로 이동
$fruits.appendChild($apple); // Banana - Orange - Apple
// 이미 존재하는 $banana 노드를
// #fruits 요소의 마지막 자식 노드 앞으로 이동
$fruits.insertBefore($banana, $fruits.lastElementChild);
// Orange - Banana - Apple
</script>Node.prototype.cloneNode([deep:true | false])- ┣ 노드의 사본을 생성하여 반환함
- ┣ 매개변수 deep :
true를 인수로 전달하면 - ┣
노드를 깊은 복사(deep copy)하여 - ┣
모든 자손 노드가 포함된 사본을 생성하고 - ┣ 1.
생략, 또는 2.false를 전달하면 - ┣ 얕은 복사(shallow copy)하여
- ┣ 노드 자신만의 사본을 생성함
- ┣
얕은 복사로 생성된 요소 노드는 - ┣ 자손 노드를 복사하지 않으므로
- ┗
텍스트 노드도 없음
<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);
// Apple
// Banana
// - Apple
// - Banana
</script>-
Node.prototype.replaceChild(newChild, oldChild)메서드 -
┣ 자신을 호출한 노드의 자식 노드를
-
┣ 다른 노드로 교체함
-
┣
첫 번째 매개변수 newChild: -
┣ 교체할 새로운 노드를 인수로 전달하고
-
┣
두 번째 매개변수 oldChild: -
┣ 이미 존재하는 교체될 노드를 인수로 전달함
-
┣ oldChild 매개변수에 인수로 전달한 노드는
-
┗
replaceChild 메서드를 호출한 노드의 자식 노드이어야 함 -
즉 : replaceChild 메서드 :
-
┣ 자신을 호출한 노드의 자식 노드인
-
┣
oldChild 노드를 newChild 노드로 교체함 -
┗
이때 oldChild 노드는 DOM에서 제거됨
$fruits.replaceChild($newChild, $fruits.firstElementChild);Node.prototype.removeChild(child)메서드 :- ┣
child 매개변수에 인수로 전달한 노드를 - ┣
DOM에서 삭제하게 됨 - ┣ 단 : 인수 전달 노드는 removeChild 메서드를
- ┗ 호출한 노드의 자식 노드이어야 함
$fruits.removeChild($fruits.lastElementChild);- HTML 문서의 구성 요소인
- ┣ HTML 요소는 여러 개의
어트리뷰트(속성)을 가질 수 있음 - ┣ HTML 요소의 동작을 제어하기 위한 추가적인 정보를
- ┗ 제공하는 어트리뷰트는 다음과 같은 형식으로 사용됨
<input id="user" type="text" value="" ungmo2 />-
글로벌 어트리뷰트
-
┣ 1.
id -
┣ 2.
class -
┣ 3.
style -
┣ 4.
title -
┣ 5.
lang -
┣ 6.
tabindex -
┣ 7.
draggable -
┗ 8.
hidden -
이벤트 핸들러 어트리뷰트
-
┣ 1.
onclick -
┣ 2.
onchange -
┣ 3.
onblur -
┣ 4.
oninput -
┣ 5.
onkeypress -
┣ 6.
onmouseover -
┣ 7.
onsubmit -
┗ 8.
onload -
위의 것들은 모든 HTML 요소에 공통적으로
-
┣ 사용이 가능하지만
-
┣
특정 HTML 요소에만 한정적으로 사용 가능한 -
┗ 어트리뷰트도 존재함
-
HTML 문서가 파싱될 때
-
┣ HTML 요소의 어트리뷰트(이하 HTML 어트리뷰트)
-
┣
어트리뷰트 노드로 변환되어 -
┣
요소 노드와 연결됨 -
┣ 이때 :
HTML 어트리뷰트 하나당 하나의 -
┗
어트리뷰트 노드가 생성됨 -
이때 모든 어트리뷰트 노드의 참조 :
-
┣
유사 배열 객체이자이터러블인 -
┣
NamedNodeMap객체에 담겨서 요소 노드의 -
┗
attributes 프로퍼티에 저장됨 -
Element.prototype.attributes프로퍼티로 -
┣ 요소 노드의 모든 어트리뷰트 노드를 취득이 가능함
-
┣ attributes 프로퍼티 : getter만 존재하는
-
┣ 읽기 전용
접근자 프로퍼티이며 -
┣ 요소 노드의 모든 어트리뷰트 노드에 참조가 담긴
-
┗
NamedNodeMap 객체를 반환함
-
요소 노드의 attributes 프로퍼티는
-
┣
getter만 존재하는 읽기 전용 접근자 프로퍼티 -
┣ 이므로 HTML 어트리뷰트 값을 취득할 수 있지만
-
┣ 변경할 수 없음
-
┣ 또한
attributes.id.value와 같이 -
┣ attributes 프로퍼티를 통해야만
-
┣ HTML 어트리뷰트 값을 취득할 수 있기 때문에
-
┗
불편함이 존재함 -
Element.prototype.getAttribute/setAttribute메서드를 -
┣ 사용하면 attributes 프로퍼티를 통하지 않고
-
┣
요소 노드에서 메서드를 통해 직접 -
┣
HTML 어트리뷰트 값을 취득하거나 변경할 수 있어서 -
┗ 편리함
<body>
<input id="user" type="text" value="hi" />
</body>
<script>
const $input = document.getElementById('user');
// value 어트리뷰트 값을 취득
const inputValue = $input.getAttribute('value');
console.log(inputValue); // hi
// value 어트리뷰트 값을 변경
$input.setAttribute('value', 'foo');
console.log($input.getAttribute('value')); // foo
</script>- 특정 어트리뷰트가 존재하는지 확인하려면
- ┣
Element.prototype.hasAttribute(attributeName) - ┣ 메서드를 사용하고
- ┣ 삭제 :
- ┗
Element.prototype.removeAttribute(attributeName)사용
-
요소 노드 객체에는 HTML 어트리뷰트에 대응하는
-
┣ 프로퍼티(이하 DOM 프로퍼티)가 존재함
-
┣ 이
DOM 프로퍼티들은 HTML 어트리뷰트 값을 -
┗
초기값으로 가지고 있음 -
DOM 프로퍼티 : setter, getter 모두 존재하는
-
┣ 접근자 프로퍼티임
-
┗
따라서 DOM 프로퍼티 : 참조와 변경이 가능함
<body>
<input id="user" type="text" value="foo" />
<script>
const $input = document.getElementById('user');
// 요소 노드의 value 프로퍼티 값을 변경
$input.value = 'foo';
// 요소 노드의 value 프로퍼티 값을 참조
console.log($input.value); // foo
</script>
</body>- 이처럼 HTML 어트리뷰트 :
- ┗ 다음과 같이
DOM에서 중복 관리되고 있는 것 처럼 보임
- 요소 노드의 attributes 프로퍼티에서 관리하는
- ┗ 어트리뷰트 노드
- HTML 어트리뷰트에 대응하는 요소 노드의 프로퍼티
-
┗ (DOM 프로퍼티)
-
HTML 어트리뷰트 : DOM 에서 중복관리 되는게 아님
-
┣ 역할 : HTML 요소의 초기 상태를 지정하는 것
-
┣ 즉 → HTML 어트리뷰트 값은 HTML 요소의 초기 상태를
-
┗ 의미하며 이는 변하지않음
-
input 요소의 value 어트리뷰트 :
-
┣ 노드로 변환되어 요소 노드의
-
┣ attributes 프로퍼티에 저장됨
-
┣ 이와는 별도로 value 어트리뷰트 값은
-
┣ 요소 노드의 value 프로퍼티에 할당됨
-
┣ 따라서 input 요소의 요소 노드가 생성되어
-
┣
첫 렌더링이 끝난 시점까지 -
┣ 어트리뷰트 노드의 어트리뷰트 값과
-
┣ value 프로퍼티에 할당된 값은
-
┗
HTML 어트리뷰트 값과 동일함 -
하지만 첫 렌더링 이후 사용자가
-
┣
input 요소에 무언가를 입력하면 -
┗ 상황은 달라지게됨
-
고로 요소 노드 :
-
┣ 2개의 상태를 관리함
-
┣ 즉 : 1. 초기 상태, 2. 최신 상태
-
┣ 요소 노드의
초기 상태: -
┣
어트리뷰트 노드가 관리 -
┣ 요소 노드의
최신 상태: -
┗
DOM 프로퍼티가 관리
-
HTML 어트리뷰트로 지정한
-
┣ HTML 요소의 초기 상태 :
-
┣
어트리뷰트 노드에서 관리함 -
┣ 사용자의 입력에 대해서
-
┣ 변경되어도 변하지 않고
-
┗
초기 상태를 유지함 -
어트리뷰트 노드가 관리하는
-
┣ 초기 상태 값을 취득하거나
-
┣ 변경하려면
-
┣
getAttribute/setAttribute -
┣ 메서드를 사용함
-
┣ getAttribute 메서드로 취득한 값 :
-
┣ 어트리뷰트 노드에서 관리하는
-
┗
HTML 어트리뷰트 값 → 즉 : 초기 상태 값임
HTML 요소에 지정한 어트리뷰트 값
사용자 입력에 의해
┣ 변하지 않으므로
┗ 언제나 동일함
// attributes 프로퍼티에 저장된
// value 어트리뷰트 값을 취득함
// 결과는 언제나 동일함
document.getElementById('user').getAttribute('value'); // foosetAttribute메서드 :- ┣ 어트리뷰트 노드에서 관리하는
- ┣ HTML 요소에 지정한 어트리뷰트 값
- ┗
즉 : 초기 상태 값을 변경함
// attributes 프로퍼티에 저장된
// value 어트리뷰트 값을 변경함
// 즉 : 초기 상태 값을 변경함
document.getElementById('user').setAttribute('value', 'hi');-
사용자가 입력한 최신 상태 :
-
┣ HTML 어트리뷰트에 대응하는
-
┣ 요소 노드의 DOM 프로퍼티가 관리함
-
┣ DOM 프로퍼티 :
-
┣
사용자의 입력에 의한 상태 변화에 반응하여 -
┗
언제나 최신 상태를 유지함 -
DOM 프로퍼티로 취득한 값: -
┣ HTML 요소의
최신 상태 값을 의미함 -
┣ 이 최신 상태 값은 사용자 입력에 의해
-
┣ 언제든지 동적으로 변경되며
-
┣
최신 상태를 유지함 -
┣ 이에 반해
getAttribute : 언제나 초기 상태 값 -
┗
변하지 않음
<html>
<input id="user" type="text" value="foo" />
<script>
const $input = document.getElementById('user');
// 사용자가 input 요소의 입력 필드에
// 값을 입력할 때마다 input 요소 노드의
// value 프로퍼티 값
// 즉 : 최신 상태 값을 취득함
$input.oninput = () => {
console.log('value 프로퍼티의 값', $input.value);
};
//getAttribute 메서드로 취득한 HTML 어트리뷰트 값
// 즉 : 초기 상태 값은 변하지 않고 유지됨
console.log('value 어트리뷰트 값', $input.getAttribute('value'));
</script>
</html>-
DOM 프로퍼티에 값을 할당하는 것: -
┣ HTML 요소의 최신 상태 값을 변경하는 것을
-
┣ 의미함
-
┗ 즉 :
사용자가 상태를 변경하는 행위와 같음 -
이처럼
HTML 어트리뷰트: -
┣
HTML 요소의 초기 상태 값을 관리하고 -
┣
DOM 프로퍼티: -
┣
사용자의 입력에 의해 변경되는 -
┣ 최신 상태를 관리함
-
┣ 단 :
모든 DOM 프로퍼티가 -
┣ 사용자 입력에 의해 변경되
최신 상태를 -
┗
유지하게 되는 것은 아님 -
Ex )
-
┣ 1. input 요소의 사용자 입력에 의한
-
┣ 상태 변화 : value 프로퍼티
-
┣ 2. checkbox 요소의 사용자 입력에 의한
-
┣ 상태 변화 : checked 프포러티
-
┣ 3. id 어트리뷰트에 대응하는
-
┗ id 프로퍼티 : 사용자 입력과 무관
-
따라서 : 사용자 입력에 의한
-
┣ 상태 변화와 관계없는 id 어트리뷰트와
-
┣
id 프로퍼티 : 사용자 입력과 관계 없이 -
┗
항상 동일한 값을 유지함 -
이처럼 사용자 입력에 의한 상태 변화와 관계있는
-
┣ DOM 프로퍼티만 최신 상태 값을 관리함
-
┣ 그 외의 사용자 입력에 의한
-
┣
상태 변화와 관계없는 어트리뷰트와 -
┗
DOM 프로퍼티는 항상 동일한 값으로 연동함
- 대부분의 HTML 어트리뷰트 HTML 어트리뷰트 이름과
- ┣
동일한 DOM 프로퍼티와 1 : 1로 대응함 - ┣ 단 : 다음과 같이 HTML 어트리뷰트와
- ┣
DOM 프로퍼티가 언제나 1 : 1 로 대응하는 것은 아니며 - ┣ HTML 어트리뷰트 이름과 DOM 프러퍼티 키가 반드시
- ┗
일치하는 것도 아님
- id 어트리뷰트와 id 프로퍼티 :
- ┗ 1 : 1 대응하며, 동일한 값으로 연동함
- input 요소의 value 어트리뷰트는 value 프로퍼티와
- ┣ 1 : 1 대응함
- ┣ 하지만 :
value 어트리뷰트 : 초기 상태를 - ┗
value 프로퍼티 : 최신 상태를 가짐
-
class 어트리뷰트: className, classList 프로퍼티와 대응함 -
for 어트리뷰트: htmlFor 프로퍼티와 1 : 1 대응함 -
td 요소의 colspan 어트리뷰트:
- ┗ 대응하는 프로퍼티가 존재하지 않음
-
textContent 프로퍼티: 대응하는 어트리뷰트가 존재하지 않음 -
어트리뷰트 이름: 대소문자를 구별하지 않지만
- ┗ 대응하는 프로퍼티 키 : 카멜 케이스를 따름
getAttribute 메서드로 취득한- ┣ 어트리뷰트 값은
언제나 문자열 - ┣ 하지만 :
DOM 프로퍼티로 취득한 최신 상태 값 : - ┣ 문자열이 아닐 수도 있음
- ┣ 예를 들어 : checkbox 요소의 checked 어트리뷰트 값을
- ┗
문자열이지만 checked 프로퍼티 값은 불리언 타입임
<script>
const $checkbox = document.querySelector('input[type=checkbox]');
// getAttribute 메서드로 취득한 어트리뷰트 값은 언제나 문자열임
console.log($checkbox.getAttribute('checked')); // ''
// DOM 프로퍼티로 취득한 최신 상태 값 :
// 문자열이 아닐 수도 있음
console.log($checkbox.checked); // true
</script>- data 어트리뷰트와 dataset 프로퍼티를 사용하면
- ┣ HTML 요소에 정의한 사용자 정의 어트리뷰트와
- ┣
자바스크립트 간에 데이터를 교환이 가능함 - ┣ data 어트리뷰트 :
data-user-id,data-role - ┗ 같이
data- 접두사 다음에 임의의 이름을 붙여 사용함
<li id="1" data-user-id="7621" data-role="admin">Lee</li>- data 어트리뷰트의 값 :
- ┣ HTMLElement.dataset 프로퍼티로 취득이 가능함
- ┣ dataset 프로퍼티 : HTML 요소의 모든 어트리뷰트 정보를 제공하는
- ┣
DOMStringMap 객체를 제공함 - ┣
DOMStringMap 객체 : - ┣ data 어트리뷰트의 data-접두사 다음에
- ┣ 붙인 이름을
카멜 케이스로 변환한 프로퍼티를 가지고 있음 - ┗ 이
프로퍼티로 data 어트리뷰트의 값을 취득, 변경 가능
<script>
const users = [...document.querySelector('.users').children];
// user-id가 '7621'인 요소 노드를 취득함
const user = users.find((user) => user.dataset.userId === '7621');
// user-id가 7621인 요소 노드의 data-role 값을 변경함
user.dataset.role = 'subscriber';
</script>- data 어트리뷰트의 data- 접두사 다음에 존재하지 않는
- ┣ 이름을 키로 사용하여 dataset 프로퍼티에 할당하면
- ┣ HTML 요소에 data 어트리뷰트가 추가됨
- ┣ 이때 dataset 프로퍼티에 추가한
- ┣
카멜케이스(fooBar) 프로퍼티 키는 - ┣ data 어트리뷰트의 data- 접두사 다음에
- ┗
케밥케이스로로 자동 변경되어 추가됨
<script>
const user = users.find(user => user.dataset.userId==='7621');
// user-id가 7621인 요소 노드에 새로운 data 어트리뷰트를 추가함
user-dataset.role = 'admin';
console.log(user.dataset);
</script>HTMLElement.prototype.style프로퍼티 :- ┣ setter, getter 모두 존재하는 접근자 프로퍼티
- ┣ 요소 노드의
인라인 스타일(inline style)을 - ┗ 취득, 추가, 변경함
<script>
const $div = document.querySelector('div');
// 인라인 스타일 취득
console.log($div.style);
// 인라인 스타일 변경
$div.style.color = 'blue';
// 인라인 스타일 추가
$div.style.width = '100px';
$div.style.height = '100px';
</script>- 스타일 프로퍼티를 참조하면
- ┣ CSSStyleDeclaration 타입의
- ┣ 객체를 반환함
- ┣ CSSStyleDeclaration 객체 :
- ┣
다양한 CSS 프로퍼티에 대응하는 프로퍼티를 - ┣ 가지고 있으며
- ┣ 이 프로퍼티에 값을 할당하면 해당 CSS 프로퍼티가
- ┗
인라인 스타일로 HTML 요소에 추가되거나 변경됨
$div.style.backgroundColor = 'yellow';케밥 케이스 CSS 프로퍼티 사용
객체의 마침표 표기법 대신
┗ 대괄호 표기법을 사용함
$div.style['background-color'] = 'yellow';- 단위 지정이 필요한 CSS 프로퍼티의 값은
- ┗
반드시 단위를 지정해야 함
$div.style.width = '100px';-
.으로 시작하는 클래스 선택자를
-
┣ 사용하여
CSS class를미리 정의한 다음 -
┣ HTML 요소의 class 어트리뷰트 값을
-
┗
변경하여 HTML 요소의 스타일을 변경이 가능함 -
이때 HTML 요소의 class 조작하면
-
┣ class 어트리뷰트에 대응하는
-
┗
요소 노드의 DOM 프로퍼티를 사용함 -
단 : class 어트리뷰트에 대응하는
-
┣ DOM 프로퍼티 : class가 아닌
-
┣
className과 classList임 -
┗
JS에서 class는 예약어임
-
Element.prototype.className프로퍼티 -
┣ setter, getter 모두 존재하는
접근자 프로퍼티로서 -
┣ HTML 요소의 class 어트리뷰트 값을 취득하거나
-
┗ 변경함
-
요소 노드의
className 프로퍼티를 참조하면 -
┣ class 어트리뷰트 값을 문자열로 반환하고
-
┣ 요소 노드의 className 프로퍼티에 문자열을 할당하면
-
┗
class 어트리뷰트 값을 할당한 문자열 변경함
<script>
const $box = document.querySelector('.box');
// .box 요소의 class 어트리뷰트 값을 취득
console.log($box.className); // 'box red'
// .box 요소의 class 어트리뷰트 값 중에서
// 'red'만 'blue'로 변경
$box.className = $box.className.replace('red', 'blue');
</script>- className 프로퍼티 :
- ┣ 문자열을 반환하므로
- ┣
공백으로 구분된 여러 개 클래스 - ┗
반환하는 경우 다루기 불편
-
Element.prototype.classList 프로퍼티: -
┣ class 어트리뷰트 정보를 담은
-
┗
DOMTokenList 객체를 반환함 -
DOMTokenList 객체: -
┣ class 어트리뷰트의 정보를 나타내는
-
┣ 컬렉션 객체로서
-
┣ 1.
유사 배열 객체 -
┣ 2.
이터러블임 -
┣ DOMTokenList 객체 :
-
┗ 다음과 같은
유용한 메서드 제공
add메서드 : 인수로 전달한- ┣ 1개 이상의 문자열을
- ┗
class 어트리뷰트 값으로 추가함
$box.classList.add('foo'); // class="box red foo"
$box.classList.add('bar', 'baz'); // class="box red foo bar baz"remove메서드 :- ┣ 인수로 전달한 문자열과 일치하는
- ┣
클래스를 class 어트리뷰트에서 삭제함 - ┣ 일치하는 클래스가 없으면 :
- ┗
에러 없이 무시됨
$box.classList.remove('foo');- item 메서드 :
- ┣ 인수로 전달한
index에 해당하는 - ┗
클래스를 class 어트리뷰트에서 반환함
$box.classList.item(0);- contains 메서드 :
- ┣ 인수로 전달한 문자열과 일치하는
- ┣ 클래스가 class 어트리뷰트에
- ┗ 포함되어 있는지 확인함
$box.classList.contains('box'); // true- replace 메서드 :
- ┣ class 어트리뷰트에서
- ┣
첫 번째 인수로 전달한 문자열을 - ┗
두 번째 인수로 전달한 문자열로 변경함
$box.classList.replace('red', 'blue'); // class "box blue"- toggle 메서드 :
- ┣ class 어트리뷰트에 인수로 전달한
- ┣
문자열과 일치하는 클래스가 존재하면: - ┣
제거하고 - ┗
존재하지 않으면 : 추가함
$box.classList.toggle('foo'); // class = "box blue foo"- 두 번째 인수로 불리언 값으로
- ┣ 평가되는 조건식을 전달이 가능함
- ┣ 이때 조건식의 평가 결과가
- ┣
true이면 : class 어트리뷰트에 - ┣ 강제로 첫 번째 인수로 전달받은 문자열을
- ┣
추가하고 - ┗
false이면 : 강제로제거함
$box.classList.toggle('foo', false); // class = "box blue"-
style 프로퍼티 :
-
┣
인라인 스타일만 반환함 -
┣ 따라서 1.
클래스를 적용한 스타일이나 -
┣ 2.
상속을 통해서 적용된 스타일: -
┗
style 프로퍼티로 참조가 불가능함 -
HTML 요소에 적용되어 있는
-
┣ 모든 CSS 스타일을 참조하려면
-
┗
getComputedStyle 메서드를 사용함 -
window.getComputedStyle(element, [, pseudo])메서드 : -
┣ 첫 번째 인수 : element로 전달한 요소 노드에
-
┣ 적용되어 있는 평가된 스타일을
-
┣
CSSStyleDeclaration 객체에 담아 반환함 -
┣ 평가된 스타일(computed style) :
-
┣ 요소 노드에 적용되어 있는 모든 스타일
-
┣ 즉 : 1.
링크 스타일, 2.임베딩 스타일, -
┣ 3.
인라인 스타일, 4.JS에서 적용된 스타일 -
┣ 5.
상속된 스타일, 5.기본(user agent) 스타일 -
┣ 모든 스타일이 조합되어
최종적으로 적용된 -
┗ 스타일을 의미함
-
getComputedStyle 메서드 두 번째 인수
-
┣
pseudo로 :after, :before와 같은 의사 요소를 -
┣ 지정하는 문자열을 전달이 가능함
-
┣
의사 요소가 아닌 일반 요소의 경우 -
┗
두 번째 인수는 생략함
<!DOCTYPE html>
<html>
<head>
<style>
.box:after {
content: 'Hello';
}
</style>
</head>
<body>
<div class="box">Box</div>
<script>
const $box = document.querySelector('.box');
// 의사 요소 : before의 스타일을 취득함
const computedStyle = window.getComputedStyle($box, ':before');
console.log(computedStyle.content); // "Hello"
</script>
</body>
</html>-
HTML과 DOM 표준 :
-
┣
W3C(World Wide Consortium) -
┣
WHATWG(Web HyperText Application Technology) -
┗ 이라는 두 단체가 나름대로 협력하면서 공통된 표준
-
18년 4월부터 구글, 애플,
-
┣ MS, 모질라로 구성된
-
┣ 4개의 주류 브라우저 벤더사가 주도하는
-
┣
WHATWG이 단일 표준을 내놓기로 -
┗
두 단체가 합의
DOM의 버전
| 레벨 |
|---|
| DOM Level 1 |
| DOM Level 2 |
| DOM Level 3 |
| DOM Level 4 |