DOM #2
Replies: 6 comments 2 replies
-
DOMDOM: Document Object Model의 약자로 HTML 문서의 계층적 구조와 정보를 표현하며 이를 제어할 수 있는 API를 제공하는 트리 자료구조입니다. NodeHTML element는 렌더링 엔진에 의해 파싱되어 DOM을 구성하는 element node 객체로 변환됩니다.
DOM은 간단히 말하면 Node 객체들로 구성된 트리 자료구조라고 할 수 있습니다. Node 객체의 타입document node
element node
attribute node
text node
노드 객체의 상속 구조위 그림과 같이 모든 노드 객체는 Object, EventTarget, Node 인터페이스를 상속받습니다. 따라서 모든 노드 객체는 Event를 발생시킬 수 있는 것입니다. 또한 트리 탐색 기능, 노드 정보 제공 기능등과 같은 Node 관련 기능들을 Node 인터페이스가 제공합니다. HTMLElement가 갖는 공통적인 기능은 HTMLElement 인터페이스가 제공합니다. 이렇게 DOM은 HTML 문서의 계층적 구조와 정보를 표현하는 것은 물론, 노드 타입에 따라 필요한 기능을 프로퍼티나 메서드의 집합인 DOM API로 제공합니다. 이 DOM API를 통해 HTML의 구조나 내용 또는 스타일 등을 동적으로 수정할 수 있습니다. element 노드 가져오기id를 이용
tagname을 이용
class를 이용Document.prototype.getElementsByClassName (document 전체) Element.prototype.getElementsByClassName (특정 element 노드의 자손)
CSS 선택자를 이용CSS 선택자는 스타일을 적용하고자 하는 HTML 요소를 특정할 때 사용하는 문법입니다. /* 전체 선택자 */
* {...}
/* 태그 선택자 */
p {...}
/* id 선택자 */
#foo {...}
/* class 선택자 */
.foo {...}
/* 어트리뷰트 선택자 */
input[type=text] {...}
/* 후손 선택자: div 요소의 후손 요소 중 p 요소를 모두 선택 */
div p {...}
/* 자식 선택자 */
div > p {...}
/* 인접 형제 선택자: p 요소의 형제 요소 중에 p 요소 바로 뒤에 위치하는 ul 요소 선택 */
p + ul {...}
/* 일반 형제 선택자: p 요소의 형제 요소 중에 P 요소 뒤에 위치하는 ul 요소를 모두 선택 */
p ~ ul {...}
/* 가상 클래스 선택자: hover 상태인 a 요소를 모두 선택 */
a:hover {...}
/* 가상 요소 선택자: p 요소의 콘텐츠의 앞에 위치하는 공간을 선택 */
p::before {...} 인수로 전달한 CSS 선택자를 만족시키는 하나의 element 노드를 탐색하여 반환합니다. Document.prototype.querySelector Element.prototype.querySelector
Document.prototype.querySelectorAll Element.prototype.querySelectorAll
HTMLCollection, NodeListHTMLCollection과 NodeList는 모두 유사 배열 객체이면서 이터러블입니다. 따라서 이 둘의 중요한 특징은 노드 객체 의 상태 변화를 실시간으로 반영하는 HTMLCollection <!DOCTYPE html>
<head>
<style>
.red {
color: red;
}
.blue {
color: blue;
}
</style>
</head>
<html lang="en">
<body>
<ul id="fruits">
<li class="red">Apple</li>
<li class="red">Banana</li>
<li class="red">Orange</li>
</ul>
</body>
<script>
const $elems = document.getElementsByClassName('red');
console.log($elems);
for (let i = 0; i < $elems.length; i++) {
$elems[i].className = 'blue';
}
console.log($elems); // HTMLCollection(1)[li.red] !!
</script>
</html> 왜 이런일이 발생하느냐?
그럼 어쩌란거냐?
더 간단한 해결책은 HTMLCollection 객체 자체를 사용하지 않는 방법입니다. 유사 배열객체이자 이터러블이기 때문에 HTMLCollection 객체를 쉽게 배열로 변경할 수 있고 이렇게 사용하시면 됩니다. NodeList querySelectorAll 메서드를 사용해서 HTMLCollection 객체의 부작용을 막을 수도 있습니다. querySelectorAll 메서드는 NodeList 객체를 반환하는데, 이때 NodeList는 살아있지 않은(non-live) 객체입니다. <!DOCTYPE html>
<head>
<style>
.red {
color: red;
}
.blue {
color: blue;
}
</style>
</head>
<html lang="en">
<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 요소의 자식 노드는 공백 텍스트 노드를 포함해 모두 5개.
console.log(childNodes); //NodeList(5) [text, li, text, li, text]
for (let i = 0; i < childNodes.length; i++) {
// removeChild 메서드가 호출될 때마다 NodeList 객체인 childNodes가 실시간으로 변경됨.
// 따라서 1,3,5만 삭제됨
$fruits.removeChild(childNodes[i]);
}
console.log(childNodes); // NodeList(2) [li,li]
</script>
</html> 그러나 위의 예시와 같이 childNodes 프로퍼티가 반환하는 NodeList 각채는 살아있는 객체가 되므로 주의해야 합니다. 결론을 내자면 HTMLCollection이나 NodeList 객체를 배열로 변환하여 사용하면 이런 부작용을 다 해결할 수 있다는 이야기입니다. node 탐색DOM 트리 상의 노드를 탐색할 수 있도록 Node, Element 인터페이스는 트리 탐색 프로퍼티를 제공합니다. 참고: 노드 탐색 프로퍼티는 모두 읽기 전용 접근자 프로퍼티기 때문에 값을 할당하면 아무런 에러 없이 무시됩니다. 공백 텍스트 노드
자식 노드 탐색<!DOCTYPE html>
<html lang="en">
<body>
<ul id="fruits">
<li class="apple">Apple</li>
<li class="banana">Banana</li>
<li class="orange">Orange</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
// childNoes: 요소 노드, 텍스트 노드를 포함한 모든 자식 요소 탐색
console.log($fruits.childNodes); // NodeList(7) [text, li.apple, text, li.banana, text, li.orange, text]
// children: element 노드만 탐색
console.log($fruits.children); // HTMLCollections(3) [li.apple, li.banana, li.orange]
// firstChild: 첫 번째 자식 노드를 탐색한다(텍스트 노드 반환 가능).
console.log($fruits.firstChild); // #text
// lastChild: 첫 번째 자식 노드를 탐색한다(element 노드 중에서)
console.log($fruits.firstElementChild); // li.apple
</script>
</html> 자식 노드 존재 확인
자식 중에서 ‘element 노드’ 존재 확인
부모 노드 탐색Node.prototype.parentNode 프로퍼티 사용 element node의 텍스트 조작nodeValue 앞서 살펴본 노드 탐색, 노드 정보 프로퍼티는 모두 읽기 전용 접근자 프로퍼티입니다. Node.prototype.nodeValue는 참조와 할당 모두 가능합니다. <!DOCTYPE html>
<html lang="en">
<body>
<div id="foo">Hello</div>
</body>
<script>
// 1. #foo 요소 노드의 자식 노드인 텍스트 노드를 취득한다.
const $textNode = document.getElementById('foo').firstChild;
// 2. nodeValue 프로퍼티를 사용하여 텍스트 노드의 값을 변경한다.
$textNode.nodeValue = 'world';
console.log($textNode.nodeValue); // world
</script>
</html> 이 때 주의할 점은 텍스트 노드가 아닌 노드 객체의 nodeValue 프로퍼티를 참조하면 null을 반환한다는 점입니다. textContent Node.prototype.textContent element 노드의 textContent 프로퍼티를 참조하면 시작 태그와 종료 태그 사이의 텍스트를 모두 반환하며 HTML 마크업은 무시됩니다. <!DOCTYPE html>
<html lang="en">
<body>
<div id="foo">Hello <span>world!</span></div>
</body>
<script>
console.log(document.getElementById('foo').textContent); // Hello world!
</script>
</html> nodeValue를 사용하는 것과 비교했을 때 코드가 더 간결한 장점이 있습니다. element 노드의 textContent 프로퍼티에 문자열을 할당하면 모든 자식 노드가 제거되고 할당한 문자열이 텍스트로 추가됩니다. (HTML 마크업 파싱 안되는 것 주의!) <!DOCTYPE html>
<html lang="en">
<body>
<div id="foo">Hello <span>world!</span></div>
</body>
<script>
document.getElementById('foo').textContent = 'Hi <span>there!</span>';
</script>
</html>
DOM 조작DOM 조작은 새로운 노드를 생성하여 DOM에 추가하거나 기존 노드를 삭제 또는 교체하는 것을 말합니다. DOM 조작에 의해 DOM에 새로운 노드가 추가되거나 삭제되면 리플로우와 리페인트가 발생하는 원인이 되므로 성능에 영향을 줍니다. innerHTMLElement.prototype.innerHTML element 노드의 콘텐츠 영역(시작 태그와 종료 태그 사이) 내에 포함된 모든 HTML 마크업을 문자열로 반환합니다. <!DOCTYPE html>
<html lang="en">
<body>
<div id="foo">Hello <span>world!</span></div>
</body>
<script>
console.log(document.getElementById('foo').innerHTML);
// Hello <span>world!</span>
</script>
</html> 만약 element 노드의 innerHTML 프로퍼티에 문자열을 할당하면 element 노드의 모든 자식 노드가 제거되고 할당한 문자열에 포함된 HTML 마크업이 파싱되어 element 노드의 자식 노드로 DOM에 반영됩니다. 주의
단점
insertAdjacentHTMLElement.prototype.insertAdjacentHTML(position, DOMString) 기존 요소를 제거하지 않으면서 위치를 지정해 새로운 요소를 삽입합니다. 첫 번째 인수로 전달할 수 있는 문자열은 아래와 같습니다. insertAdjacentHTML은 기존 요소에 영향을 주지 않고 새롭게 삽입될 요소만을 파싱하여 자식 요소로 추가하므로 innerHTML보다 효율적입니다. 그러나 크로스 사이트 스크립팅 공격에 취약하다는 점은 동일합니다. node 생성과 추가<!DOCTYPE html>
<html lang="en">
<body>
<div id="foo">Hello <span>world!</span></div>
</body>
<script>
// element 노드 생성
const $li = document.createElement('li');
// text 노드 생성
const textNode = document.createTextNode('Banana');
// text 노드를 $li element 노드의 자식 노드로 추가
$li.appendChild(textNode);
// $li element 노드를 #fruits element 노드의 마지막 자식 노드로 추가
$fruits.appendChild($li)
// 기존의 DOM에 element 노드를 추가하는 건 이 과정 뿐입니다.
// DOM이 한 번 변경됐으므로 리플로우와 리페인트가 이 때 한번만 발생합니다.
</script>
</html> 추가로 복수의 노드를 생성할 때, 부모 노드가 없어 기존 DOM과는 별도로 존재하는 DocumentFragment 노드를 사용하는데 사용방법은 다음과 같습니다.
참고
|
Beta Was this translation helpful? Give feedback.
-
네이티브 객체(Native Objects)와 호스트 객체(Host Objects)네이티브 객체네이티브 객체는 특정 환경(브라우저 등)에 종속 되지 않는 JavaScript 표준 내장 객체(Built-in objects)를 의미한다. 표준 내장 객체는 숫자, 텍스트, 오류 객체 등이 있다. 네이티브 객체 종류
try {
// foo();
throw new Error('Whoops!');
} catch (e) {
console.log(e.name + ': ' + e.message);
}
호스트 객체
DocumentDocument 인터페이스는 브라우저가 불러온 웹 페이지를 나타내며, DOM tree의 진입점이다. Document는 Node와 EventTarget 인터페이스도 상속한다. DOM Tree브라우저의 렌더링 엔진은 웹 문서를 로드한 후, 파싱하여 웹 문서를 브라우저가 이해할 수 있는 구조로 구성하여 메모리에 적재하는데 이를 DOM이라 한다.
<html>
<head></head>
<body>
"안녕하세요"
</body>
</html> Shadow DOMDOM에 비밀 공간(?)을 만들 수 있다. shadow DOM 트리는 shadow root로부터 시작되어 원하는 모든 요소의 안에 부착될 수 있다. 구조는 다음과 같다.
방법<body>
<div id ="root"></div>
<script>
document.querySelector("#root").attachShadow({mode: 'open'}); // Shadow root 만들기
document.querySelector("#root").shadowRoot.innerHTML = "<h1>FE</h1>" // 태그 삽입
</script>
</body>
적용 예시Element에 접근하기
Document.querySelector(), Document.querySelectorAll()
lib.dom.d.ts 로 살펴보기/**
* Returns the first element that is a descendant of node that matches selectors.
*/
querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
querySelector<K extends keyof SVGElementTagNameMap>(selectors: K): SVGElementTagNameMap[K] | null;
querySelector<E extends Element = Element>(selectors: string): E | null;
/**
* Returns all element descendants of node that match selectors.
*/
querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;
querySelectorAll<K extends keyof SVGElementTagNameMap>(selectors: K): NodeListOf<SVGElementTagNameMap[K]>;
querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>; querySelectorAll
Document.getElementById()매번 DFS로 id에 해당하는 엘리먼트를 찾는 것이 아니라 해시 테이블을 통해 데이터를 가져오기도 한다. WebKit -
|
Beta Was this translation helpful? Give feedback.
-
DOM SpecDOM 스펙은 W3C에서 Level 단위로 만들어지고 있다. DOM 레벨 1은 HTML, XML 문서 구조를 정의하는데 초점이 맞춰져 있었다. 이후 발표된 DOM 레벨 2, 3은 위 구조에 따른 상호작용 기능 추가 및 고급 XML 기능을 지원하는데 집중했다. DOM 레벨 3 이벤트 문서는 2000년과 2003년 사이에 개발되었으며 구현자의 추가 피드백과 관심이 있을 때까지 W3C 메모로 게시되었다. 2006년에 Recommendation Track에서 수정 및 진행을 위해 선택되었고, 현재 구현 상태와 스크립트 작성자의 요구를 반영하도록 수정되었다. Event이벤트는 다음 상황에서 발생한다.
Event.eventPhaseEvent의 읽기 전용 속성인 eventPhase는 현재 평가중인 이벤트 흐름의 단계를 나타낸다. const phase = event.eventPhase;
각 단계가 이벤트에 상수로 정의되어있어서 e.eventPhase === e.AT_TARGET 처럼 비교할 수 있다. console.log(e.eventPhase, e.NONE, e.CAPTURING_PHASE, e.AT_TARGET, e.BUBBLING_PHASE) Event Flow이벤트 객체는 DOM event flow에 의해 결정된 대로 DOM tree를 통해 전파된다. 또한, 우리는 dispatchEvent() 메서드를 사용해서 이벤트 객체를 전달할 수 있다. 디스패치 전에 stopPropagation() 가 호출되면 모든 단계를 건너뛴다. Synchronous and asynchronous events이벤트는 동기/비동기로 전달될 수 있다. 동기 이벤트는 사용자 상호작용 및 DOM 변경, 다른 이벤트 측면에 대해, 발생한 시간 순으로 가상 큐에 대해 선입선출(FIFO)과 같은 동작을 한다. 비동기 이벤트는 작업 결과가 완료될 때 전달된다. 예를 들어, document를 불러올 때, 인라인 스크립트 요소를 분석하고 실행한다. load 이벤트는 스크립트 요소로 큐에 들어가서 비동기적으로 실행된다. 비동기 이벤트이기 때문에 문서 로드 중에 발생하는 다른 동기 이벤트(e.g. DOMContentLoaded event)의 순서와 관련된 보장되지 않는다. Trusted Events사용자와의 상호작용 혹은 DOM 변경으로 발생한 이벤트를 Trusted Event라 한다. 그 반대의 경우로 스크립트에 의해 생성/변경되거나 createEvent(), initEvent(), EventTarget.dispatchEvent() 로 발생했을 때의 경우가 있다. (createEvent(), initEvent()는 deprecated 됐다.) Event.isTrusted 메소드(읽기 전용)는 Trusted Event를 판별해준다. Trusted Event 일 때, true이다. Untrusted Event에 대해서 click 이벤트를 제외하고는 preventDefault() 가 호출되어서 default action은 동작하지 않는다. click 이벤트가 제외된 이유는 이전 버전과의 상호 호환성 때문이다. Activation triggers and behavior특정 event targets (e.g. 링크나 버튼 element)는 activation trigger (e.g. 링크 클릭, focus를 가지고 있지않아도 해당한다.)에 따른 activation behavior (e.g. 링크 이동)를 가지고 있다. Constructing Mouse and Keyboard Events일반적으로 Event 인터페이스 또는 Event 인터페이스에서 상속된 인터페이스의 생성자가 호출되면 [DOM]에 설명된 단계를 따라야 한다. 하지만, KeyboardEvent 및 MouseEvent 인터페이스는 Event 객체의 키 수정자의 내부 상태, 특히 getModifierState() 및 getModifierState() 메서드를 사용하여 쿼리 된 내부 상태를 초기화하기 위한 추가적인 dictionary member를 제공한다. 이 부분은 Event 객체를 초기화하기 위한 DOM4 단계를 보완한다. [Exposed=(Window,Worker,AudioWorklet)]
interface Event {
constructor(DOMString type, optional EventInit eventInitDict = {});
readonly attribute DOMString type;
readonly attribute EventTarget? target;
readonly attribute EventTarget? srcElement; // legacy
readonly attribute EventTarget? currentTarget;
sequence<EventTarget> composedPath();
const unsigned short NONE = 0;
const unsigned short CAPTURING_PHASE = 1;
const unsigned short AT_TARGET = 2;
const unsigned short BUBBLING_PHASE = 3;
readonly attribute unsigned short eventPhase;
undefined stopPropagation();
attribute boolean cancelBubble; // legacy alias of .stopPropagation()
undefined stopImmediatePropagation();
readonly attribute boolean bubbles;
readonly attribute boolean cancelable;
attribute boolean returnValue; // legacy
undefined preventDefault();
readonly attribute boolean defaultPrevented;
readonly attribute boolean composed;
[LegacyUnforgeable] readonly attribute boolean isTrusted;
readonly attribute DOMHighResTimeStamp timeStamp;
undefined initEvent(DOMString type, optional boolean bubbles = false, optional boolean cancelable = false); // legacy
};
dictionary EventInit {
boolean bubbles = false;
boolean cancelable = false;
boolean composed = false;
}; Event Typeshttps://www.w3.org/TR/DOM-Level-3-Events/#event-types 참고 |
Beta Was this translation helpful? Give feedback.
-
이벤트 버블링 / 캡쳐링document의 이벤트는 DOM 구조를 타고 전이(propagation)된다. 이 전이되는 방식이 버블링과 캡쳐링이다. 버블링
<div onclick="alert('Parent')">
<div onclick="alert('Middle')">
<div onclick="alert('Child')">안녕하세요</p>
</div>
</form> 위의 예제에서 '안녕하세요' 텍스트를 클릭하게 되면, 'Child' 알럿이 뜨는 것은 자명하다. 하지만 여기서 끝나는 것이 아니라, 버블링의 특징가장 중요한 것은 버블링은 이벤트 핸들러의 수행순서가 그림과 같이 DOM 트리 구조 상에서 최하위 요소에서 최상위 요소로 거슬러 올라간다는 것이다.
캡처링
버블링이 이벤트가 발생하여 핸들러를 통해 처리하는 방식이라면, 이벤트가 발생하기까지 타겟 요소로 전달하는 과정으로 볼 수 있다. 캡처링의 특징가장 중요한 것은 캡처링은 DOM 트리 구조 상에서 최상위 요소에서 최하위 요소로 내려간다는 것이다.
이벤트 위임
이벤트 위임을 구현하기 위해서는 이벤트 타겟 요소를 알아야한다. 이벤트 위임에서는 이벤트가 발생하는 요소와 이벤트 핸들러가 등록된 요소 즉, 실제 이벤트를 처리하는 요소가 다르다. 이를 명확하게 구별하기 위해서 이벤트가 지칭하는 이벤트 객체
event.target - 실제 이벤트가 발생된 요소event.currentTarget - 해당 이벤트의 이벤트 리스너가 바인딩된 요소<div class="parent" onclick="parent">
<div class="middle">
<div class="child">안녕하세요</div>
</div>
</form> 위의 예제에서 최하위 요소인 이벤트 위임에서 중요한 것은 이벤트 핸들러를 통해 이벤트를 처리한 요소가 아닌 실제 이벤트가 발생한 요소이다. <input type="button" value="1" data-counter>
<input type="button" value="2" data-counter>
<script>
document.addEventListener('click', function(event) {
if (event.target.dataset.counter != undefined) { // 속성이 존재할 경우
event.target.value++;
}
});
</script> 최상위 객체 이벤트 위임의 장점
참고 |
Beta Was this translation helpful? Give feedback.
-
날먹 1 DocumentFragement |
Beta Was this translation helpful? Give feedback.
-
오늘의 질문
|
Beta Was this translation helpful? Give feedback.
-
주제 키워드는
✨DOM✨
입니다.
☘️ 하위 주제 키워드
참고링크
Beta Was this translation helpful? Give feedback.
All reactions