DOM (Document Object Model)
은 HTML 문서의 계층적 구조와 정보를 표현하며, 이를 제어 할 수 있는 API
, 즉 프로퍼티와 메소드를 제공하는 트리 자료구조이다.
HTML 요소
는 HTML 문서를 구성하는 개별적인 요소를 의미한다.
<div class = 'greeting'>Hello</div>
라는 태그가 있을 때
<div>
: 시작태그class
: 어트리뷰트 이름gretting
: 어트리뷰트 값Hello
: 콘텐츠 </div>
: 종료 태그로 나눌 수 있다.
렌더링 엔진은 해당 태그를 파싱하여 DOM
을 구성하는 요소 노드 객체로 변환한다.
이 때
시작 태그와 종료태그
는 요소 노드
어트리뷰트 이름과 값
은 어트리뷰트 노드
콘텐츠
는 콘텐츠 노드
로 객체가 생성된다.이렇게 생성된 객체를 계층적 구조에 따라 트리 구조를 만든다.
그래서 다음과 같은 DOM
이 생성 되었을 때 노드 별 특징을 정리해보자
Document
)DOM
트리의 최상단에 존재하는 루트 노드로서 document
객체를 가리킨다.
document
객체는 브라우저가 렌더링 한 HTML
문서 전체를 가리키는 객체로서 전역 객체 window
의 document
프로퍼티에 바인딩 되어 있다.
브라우저 환경의 모든 자바스크립트 코드는 script
태그 별로 분리되어 있어도 하나의 전역 객체인 window
를 공유한다.
즉, 문서당 document
객체는 하나로 유일하다.
모든 트리 노드의 최상단 노드로 다른 노드들에 접근하기 위한 진입점
역할을 한다.
html , body , head , ul ...
)요소를 가리키는 객체로, 부모, 자식, 형제 관계를 가지며, 이러한 정보를 구조화 한다.
요소노드는 문서의 구조를 표현한다.
class , id
)어트리뷰트가 지정된 요소 노드와 연결 되어 있다.
하지만 어트리뷰트 노드는 부모 노드가 존재하지 않으며 오로지 요소노드와만 연결되어 있다.
그럼 요소노드의 형제노드인가 ?
같은 부모 노드를 공유하지 않기 때문에 형제노드라고 보기는 어렵다.
따라서 어트리뷰트 노드에 접근하기 위해서는 필수적으로 해당 어트리뷰트 노드와 연결되어 있는 요소 노드에 접근해야 한다.
텍스트 노드는 HTML 요소의 텍스트를 가리키는 객체이다.
요소 노드의 자식 노드이며 자식 노드를 가질 수 없는 리프 노드이다.
따라서 텍스트 노드에 접근하기 위해서는 요소 노드에 접근해야 한다.
DOM
은 자신을 제어 할 수 있는 DOM API
, 즉 프로퍼티와 메소드를 제공하는 트리 자료구조라고 하였다.
이를 통해 자신의 계층 구조를 이용해 부모, 자식, 형제 노드를 탐색 할 수 있으며 어트리뷰트와 택스트를 조작 할 수 있다.
DOM
을 구성하는 노드 객체는 표준 빌트인 객체가 아닌 브라우저 환경에 따라 추가적으로 제공되는 호스트 객체
이다.
호스트 객체
호스트 객체(Host Object)는 JavaScript 환경에 따라 추가적으로 제공되는 객체로, 브라우저 환경에서는 DOM이나 BOM(Browser Object Model)과 관련된 객체들이 여기에 해당합니다. 호스트 객체는 JavaScript 엔진이 아닌 호스트 환경에서 제공되는 객체로, 브라우저에서 실행되는 JavaScript 코드에서는 브라우저가 이를 제공합니다.
하지만 노드 객체도 자바스크립트의 객체이므로 프로토타입에 의한 상속 구조를 갖는다.
모든 노드 객체는 공통적으로 Object => EventTarget => Node
의 프로토타입을 상속 받는다.
Document , HTMLDocument
를 상속 받는다.Element => HTMLElement
를 상속 받는다. (이후엔 요소 별 프로토타입을 상속 받는다.)Attr
을 상속 받는다.CharcterData
를 상속 받는다.<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=<device-width>, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input type="text" />
<script>
const $input = document.querySelector('input');
console.log(Object.getPrototypeOf($input) === HTMLInputElement.prototype);
console.log(
Object.getPrototypeOf(HTMLInputElement.prototype) ===
HTMLElement.prototype,
);
console.log(Object.getPrototypeOf(HTMLElement.prototype)) ===
Element.prototype;
console.log(Object.getPrototypeOf(Element.prototype) === Node.prototype);
console.log(
Object.getPrototypeOf(Node.prototype) === EventTarget.prototype,
);
console.log(
Object.getPrototypeOf(EventTarget.prototype) === Object.prototype,
);
</script>
</body>
</html>
상속 관계를 하단에서부터 상단으로 올라가며 확인해보면 다음과 같다.
이렇게 프로토타입 상속 관계를 통하여 요소 노드 별 공통되는 프로퍼티나 메소드를 효과적으로 관리 할 수 있다.
예를 들어 input
태그와 같이 Ojbect => EventTarget => Node => Element => HTMLElement => HTMLInputElement
의 프로토타입 체인을 따르는 태그만 가지고 있는 메소드는
HTMLInputElement
의 프로토타입을 통해 상속 받는다.
하지만 input
태그 뿐이 아니라 다른 div , span , p ...
등 많은 요소 태그들이 공통적으로 가지는 프로퍼티나 메소드 (예를 들어 모든 요소 태그들은 style
이란 프로퍼티를 갖는다.)들은 상위 프로토타입 체인인 Element
의 프로토타입을 상속 받는다.
또 Element
뿐이 아니라 다른 노드를 탐색해야 하는 기능은 상위 프로토타입인 Node
의 프로토타입을 통해, 또 이벤트를 발생 시킬 수 있는 기능은 상위 프로토타입인 EventTarget
의 프로토타입을 통해
효과적으로 프로퍼티와 메소드를 관리 한다.
정리
HTML
문서의 내용을DOM
을 통해 삽입, 삭제, 탐색 및 수정이 가능한 자료구조로 생성하고 , 이런 자료구조를 통해 프로토타입 체인을 구성하여 공통된 프로퍼티나 메소드를 효과적으로 관리한다.제일 중요한 내용은
DOM
을 통해 노드들에 접근하여DOM
을 자바스크립트를 통해 리플로우, 리페인트 할 수 있도록 하는DOM API
를 제공한다는 점이다.
DOM API
를 통해 요소 노드에 접근하여 삽입, 삭제 및 수정을 할 수 있다고 했다.
요소 노드 취득은 HTML 요소를 조작 하는 시작점이다.
DOM 은 요소 노드 취득을 하기 위한 다양한 메소드를 제공한다.
Document.prototype.getElementById
메소드는 인수로 전달한 id 어트리뷰트 값을 갖는 하나의 요소 노드를 탐색하여 반환한다.
포인트
- 모든 요소 노드를 탐색 할 수 있게 하기 위하여 가장 최상단 노드인
Document
의 프로토타입 메소드를 사용한다.id
는 단 하나 가질 수 있기 때문에 하나만 탐색하여 반환한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<ul>
<li id="apple">Apple</li>
<li id="banana">Banana</li>
<li id="orange">orange</li>
</ul>
<script>
const $elem = document.getElementById('apple');
$elem.style.color = 'red';
</script>
</body>
</html>
id
가 apple
인 요소 노드를 탐색하여 해당 요소 노드 객체의 style
프로퍼티에 접근하여 값을 변경하였다.
만약 해당 id
가 중복되더라도, 가장 먼저 탐색 되는 id
만 찾는다.
만약 존재하지 않는 id
를 가져오려 하면 null
값이 반환된다.
id
요소로 생성한 요소 노드는 동일한 이름의 전역 변수가 암묵적으로 선언되고, 해당 노드 객체가 선언된 전역 변수에 바인딩 된다
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="foo">foo!~~!!!!~!!</div>
<script>
console.log(foo === document.getElementById('foo')); // true
delete foo;
console.log(foo); // <div id="foo">foo!~~!!!!~!!</div>
</script>
</body>
</html>
Document.prototype / Element.prototype.getElementsByTagName
메소드는 인수로 전달한 태그 이름을 갖는 모든 요소들을 탐색하여 반환한다.
여러 개의 요소 노드 객체를 갖는 DOM
컬렉션 객체인 HTMLCollection
객체를 반환한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<ul>
<li>orange</li>
<li>banana</li>
<li>apple</li>
</ul>
<script>
$elems = document.getElementsByTagName('li');
const colors = ['red', 'blue', 'orange'];
console.log($elems);
console.log(typeof $elems);
[...$elems].forEach((elem, i) => {
elem.style.color = colors[i];
});
</script>
</body>
</html>
getElementByTagNames
메소드가 반환하는 HTMLCollection
객체는 유사 배열 객체이며 이터러블이다.
위에서 살펴보면 인덱스 역할을 하는 프로퍼티와
length
가 존재하는 것을 볼 수 있다.
모든 태그 이름을 가져오는 방법
getElementByTagName
의 인수에*
를 넣어주면 모든 태그 이름을 가져 올 수 있다.
document.getElementbyTagName
vs Element.getElementbyTageName
document
에서부터 탐색을 시작하는 getElementbyTagName
은 모든 문서 내에서 tag name
이 인수와 같은 것을 가져왔다.
이렇게 해서 가져와진 HTMLCollection
객체는 Element.prototype.getElementsByTageName
를 상속 받아 사용 할 수 있다.
HTMLCollection
객체에서getElementsByTageName
메소드를 사용하면 프로토타입 체인에서 가장 가까운 상위 체인이Element
이기 때문에Element
의 메소드가 호출된다.
class
를 이용한 요소 노드 취득Document.prototype / Element.prototype.getElementsByClassName
은 인수로 전달 받은 class
명을 갖는 모든 요소 노드를 탐색하여 반환한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<ul id="fruits">
<li class="apple">apple</li>
<li class="banana">banana</li>
<li class="orange">orange</li>
</ul>
<div class="banana">banana</div>
<script>
const $AllBananaFromDocument = document.getElementsByClassName('banana');
console.log($AllBananaFromDocument); // document 내에 존재하는 모든 클래스명 탐색
const $fruits = document.getElementById('fruits'); // id 가 fruits 인 노드 탐색
console.log(`$fruits : ${$fruits}`);
console.log($fruits);
const $BananaFromfruits = $fruits.getElementsByClassName('banana');
// id 가 fruits 인 노드에서 class 명이 banana인 노드 탐색
console.log($BananaFromfruits);
[...$BananaFromfruits].forEach((elem) => (elem.style.color = 'yellow '));
</script>
</body>
</html>
프로토타입 상속을 이용하여 노드들을 탐색하는 모습을 볼 수 있다.
이 때 특징적인 것은 2개 이상의 노드를 취득하게 하는 getElementsByClassName
은 HMTLCollection
이란 객체 타입으로 찾아진다는 것이다. (유사배열 객체이며 이터러블한)
또한 document.get ...
메소드를 이용하면 문서 내에 존재하는 모든 노드를 찾는데
이 때 찾아서 반환된 HTMLCollection
객체에서 Element.get ...
메소드를 사용하면, 찾아진 객체 내에서만 탐색이 가능하단 점이다.
CSS
선택자를 이용한 요소 노드 취득CSS
선택자는 스타일을 적용하고자 하는 HTML 요소를 특정 할 때 사용하는 문법이다.
* {} /* 모든 요소 취득 */
p {} /* 모든 p 태그 요소 취득 */
#foo {} /* id 가 foo 인 요소 취득 */
.bar {} /* class 가 bar 인 요소 취득 */
div p {} /* div 요소의 모든 하위 노드 중 p 요소 취득 */
p + ul {} /* 인접 형제 선택자 */
p ~ ul {} /* 일반 형제 선택자*/
처럼 CSS
의 선택자 문법을 이용하여 태그를 취득 할 수 있다.
Document.prototype / Element.prototype.querySelector
메소드를 이용할 수 있으며 CSS 선택자를 만족시키는 하나의 요소 노드를 탐색하여 반환한다.
CSS
선택자를 만족시키는 요소가 여러개일 경우엔 첫 번째 요소 노드만 반환한다.CSS
선택자를 만족시키는 요소 노드가 존재하지 않으면 null
값을 반환한다.CSS
선택자가 문법에 맞지 않는 경우 DOMException
에러가 발생한다.<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<ul id="fruits">
<li class="apple">apple</li>
<li class="banana">banana</li>
<li class="orange">orange</li>
</ul>
<div class="banana">banana</div>
<script>
const $bananaFromDocument = document.querySelector('.banana');
console.log($bananaFromDocument);
const $bananaFromdiv = document.querySelector('div.banana');
console.log($bananaFromdiv);
$bananaFromDocument.style.color = 'red';
$bananaFromdiv.style.color = 'orange';
</script>
</body>
</html>
좀 더 복잡한 예제를 가지고 연습해봐야겠다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style></style>
</head>
<body>
<div class="InnerWraper">
<div class="InnerItems">
<div>
<ul>
<li id="orange">orange</li>
<li id="banana">banana</li>
<li id="apple">apple</li>
</ul>
<div id="fruits">
<ul>
<li id="orange">orange</li>
<li id="banana">banana</li>
<li id="apple">apple</li>
</ul>
</div>
</div>
</div>
</div>
<script>
const $BananaFromDocument = document.querySelector('#banana');
const $BananaFromfruits = document.querySelector('#fruits>ul>#banana');
$BananaFromDocument.style.color = 'red';
$BananaFromfruits.style.color = 'orange';
</script>
</body>
</html>
아 나는 도대체 사람들 코드를 구경하다 보이는
querySelector
가 뭘까 .. .저게 그 말로만 듣던jQuery
인가 .. 그랬는데 CSS 선택자였다.!!
.querySelector
는 하나의 요소 노드만 가져온다고 했었다.
그럼 2개 이상의 노드를 가져오고 싶다면 ?
.querySelectorALL
을 사용하면 된다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style></style>
</head>
<body>
<div class="InnerWraper">
<div class="InnerItems">
<div>
<ul>
<li id="orange">orange</li>
<li id="banana">banana</li>
<li id="apple">apple</li>
</ul>
<div id="fruits">
<ul>
<li id="orange">orange</li>
<li id="banana">banana</li>
<li id="apple">apple</li>
</ul>
</div>
</div>
</div>
</div>
<script>
const $liFromDocument = document.querySelectorAll('div li');
const $bananaFromDocument = document.querySelectorAll('div #banana');
const $fruitsFromDocument = document.querySelectorAll('#fruits li');
[...$liFromDocument].forEach((li) => (li.style.color = 'blue'));
[...$fruitsFromDocument].forEach((elem) => (elem.style.color = 'pink'));
[...$bananaFromDocument].forEach(
(banana) => (banana.style.color = 'orange'),
);
</script>
</body>
</html>
querySelectorALL
을 이용하니 이전과 같은 유사배열 객체 형태로 받을 수 있음을 확인 할 수 있었다.
Element.prototype.matches
메소드는 인수로 전달한 CSS
선택자를 통해 특정 요소 노드를 취득 할 수 있는지 확인한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<ul id="fruits">
<li id="orange">orange</li>
<li id="banana">banana</li>
<li id="apple">apple</li>
</ul>
<script>
const $apple = document.querySelector('#apple');
const $banana = document.querySelector('#banana');
const query = 'ul #apple';
console.log(
`apple 은 ${query} 로 선택 할 수 있는가 ? : ${$apple.matches(query)}`,
);
console.log(
`banana 은 ${query} 로 선택 할 수 있는가 ? : ${$banana.matches(query)}`,
);
</script>
</body>
</html>
이는 이벤트 위임을 할 때 유용하다.