모자딥 39장 DOM

릿·2023년 2월 9일
0

39. DOM

  • DOM은 HTML문서의 계층적 구조와 정보를 표현하며 이를 제어할 수 있는 API(프로퍼티, 메서드)를 제공하는 트리 자료구조

39.1 노드


39.1.1 HTML요소와 노드 객체

  • HTML요소 : HTML문서를 구성하는 개별적인 요소
    중첩관계에 의한 부자관계가 형성되어 트리구조를 가지게 됨

1. 트리 자료구조

  • 트리자료구조 : 노드들의 계층구조를 표현하는 비선형 자료구조
  • DOM : 노드 객체들로 구성된 트리 자료구조

39.1.2 노드 객체의 타입

  • 렌더링 엔진은 위 HTML문서를 파싱하여 다음의 DOM을 생성함
  • 노드 객체는 총 12개의 종류가 있음

1. 문서 노드 (document node)

  • DOM트리의 최상위에 존재하는 루트 노드로 document객체를 가리킴, DOM트리의 루트 노드

2. 요소 노드 (element node)

  • HTML요소를 가리키는 객체, 문서의 구조를 표현함

3. 어트리뷰트 노드 (attribute node)

  • HTML요소의 텍스트를 가리키는 객체, 문서의 정보를 표현함, 자식 노드를 가질 수 없는 리프 노드

39.1.3 노드 객체의 상속 구조

  • DOM을 구성하는 노드 객체는 브라우저 환경에서 추가적으로 제공하는 호스트 객체로, 프로토타입에 의한 상속구조를 가짐


  • 노드 객체의 상속구조는 개발자도구에서 확인할 수 있음
  • DOM API를 통해 HTML구조나 내용 또는 스타일 등을 동적으로 조작할 수 있음

39.2. 요소 노드 취득


  • HTML의 구조나 내용 또는 스타일 등을 동적으로 조작하려면 먼저 요소 노드를 취득해야 함

39.2.1 id를 이용한 요소 노드 취득

  • Document.prototype.getElementById메서드는 인수로 전달한 id값을 갖는 첫번째 요소 노드를 탐색하여 반환함
  • 해당 요소가 존재하지 않을 경우 null반환
const $elem = document.getElementById('banana');
$elem.style.color = 'red';

39.2.2 태그 이름을 이용한 요소 노드 취득

  • Document.prototype/Element.prototype.getElementsByTagName메서드는 인수로 전달한 태그 이름을 가진 모든 요소를 탐색하여 반환함
const $elems = document.getElementsByTagName('li');
[...$elems].forEach(elem => { elem.style.color = 'red'; });
  • getElementsByTagName메서드가 반환하는 HTMLCollection객체는 유사배열객체이면서 이터러블임
  • HTML문서의 모든 요소 노드를 취득하려면 getElementsByTagName메서드의 인수로 '*'을 전달
const $all = document.getElementsByTagName('*');
  • 해당 요소가 존재하지 않을 경우, 빈 HTMLCollection객체 반환

39.2.3 class를 이용한 요소 노드 취득

  • Document.prototype/Element.prototype.getElementsByClassName메서드는 인수로 전달한 class값을 갖는 모든 요소들을 탐색하여 HTMLCollection객체를 반환함
  • 해당 요소가 존재하지 않을 경우 빈 HTMLCollectin객체 반환
const $bananasFromDocument = document.getElementByClassName('banana');

39.2.4 CSS선택자를 이용한 요소 노드 취득

  • Document.prototype/Element.prototype.querySelector메서드는 인수로 전달한 CSS선택자에 맞는 하나의 요소를 반환함
  • 해당 요소가 존재하지 않을 경우 null반환
const $elem = document.querySelector('.banana');
$elem.style.color = 'red';
  • Document.prototype/Element.prototype.querySelectorAll메서드는 인수로 전달한 CSS선택자에 맞는 모든 요소 노드를 탐색하여 NodeList객체를 반환함
  • 해당 요소가 존재하지 않을 경우 빈 NodeList객체를 반환
  • HTML문서의 모든 요소 노드를 취득하려면 인수로 '*'을 전달

메서드들 비교

  • querySelector, querySelectorAll은 getElementById, getElementBy***메서드보다 다소 느림
  • querySelector, querySelectorAll를 사용하면 구체적인 조건으로 요소 노드를 취득할 수 있다는 장점이 있음
  • id어트리뷰트가 있는 요소 노드 취득 : getElementById메서드 사용, 그 외엔 querySelector, querySelectorAll메서드 사용 권장

39.2.5 특정 요소 노드를 취득할 수 있는지 확인

  • Element.prototype.matches메서드는 인수로 전달한 CSS선택자를 통해 특정 요소 노드를 취득할 수 있는지 확인함
  • 이벤트 위임을 사용할 때 유용함
const $apple = document.querySelector('.apple');
console.log($apple.matches('#fruits > li.apple')); // true

39.2.6 HTMLCollection과 NodeList

  • HTMLCollection, NodeList 둘 다 유사배열객체이면서 이터러블임

HTMLCollection

  • 노드 객체의 상태 변화를 실시간으로 반영하는 살아있는 DOM컬렉션 객체
  • HTMLCollection객체를 배열로 전환하면 고차함수를 사용할 수 있음
[...$elems].forEach(elem => elem.className = 'blue');

NodeList

  • 실시간으로 노드 객체의 상태 변경을 반영하지 않는 객체 (단, childNodes프로퍼티가 반환하는 NodeList객체는 live객체로 동작함)
  • querySelectorAll이 반환하는 NodeList객체는 NodeList.prototype.forEach/item/entryes/keys/values메서도를 상속받아 사용할 수 있음
  • 안전하게 DOM컬렉션을 사용하려면 배열로 변환하여 사용하는 것을 권장함

39.3 노드 탐색


  • 요소 노드를 취득해 부모, 형제, 자식 노드 탐색이 가능함

노드 탐색 프로퍼티

  • 노드 탐색 프로퍼티는 모두 접근자 프로퍼티로, 참조만 가능한 읽기 전용임

1. Node.prototype

  • parentNode, previousSibling, firstChild, childNodes

2. Element.prototype

  • previousElementSibling, nextElementSibling, children

39.3.1 공백 텍스트 노드

  • HTML요소 사이의 스페이스, 탭, 줄바꿈 등의 공백 문자가 생성하는 노드

39.3.2 자식 노드 탐색


39.3.3 자식 노드 존재 확인

  • Node.prototype.hasChildNodes메서드를 사용하여 존재하면 true/존재하지 않으면 false를 반환

39.3.4 요소 노드의 텍스트 노드 탐색

  • firstChild프로퍼티로 접근 가능
console.log(document.getElementById('foo').firstChild);

39.3.5 부모 노드 탐색

  • Node.prototype.parentNode프로퍼티를 사용

39.3.6 형제 노드 탐색

  • Node.prototype.previousSibling/nextSibling, Element.prototype.previousElementSibling.nextElementSibling

39.4 노드 정보 취득


  • Node.prototype.nodeType : 노드 타입을 나타내는 상수를 반환
  • Node.prototype.nodeName : 노드 이름을 문자열로 반환

39.5 요소 노드의 텍스트 조작


39.5.1 nodeValue

  • Node.prototype.nodeValue프로퍼티는 노드 객체의 값을 반환함
  • 참조, 할당 둘 다 가능

39.5.2 textContent

  • Node.prototype.textContent프로퍼티는 요소 노드의 텍스트와 모든 자손 노드의 텍스트를 모두 취득하거나 변경함
  • 참조, 할당 둘 다 가능
  • textContent와 유사하게 동작하는 innerText프로퍼티는 CSS속성에 의해 해당 텍스트를 반환하지 않는 일이 생기거나 느리기 때문에 추천하지 않음

39.6 DOM 조작


  • DOM조작은 새로운 노드를 생성하여 DOM에 추가하거나 기존 노드를 삭제 또는 교체하는 것
  • DOM조작에 의해 새로운 노드가 추가되거나 삭제되면 리플로우와 리페인트 발생

39.6.1 innerHTML

  • Element.prototype.innerHTML프로퍼티는 요소 노드의 HTML마크업을 취득하거나 변경함
  • 참조, 할당 둘 다 가능
  • HTML 마크업 문자열을 파싱하므로 크로스 사이트 스크립팅 공격에 취약함

39.6.2 insertAdjacentHTML메서드

  • Element.prototype.insertAdjacentHTML메서드는 기존 요소를 제거하지 않으면서 위치를 지정해 새로운 요소를 삽입함
  • HTML 마크업 문자열을 파싱하므로 크로스 사이트 스크립팅 공격에 취약함

39.6.3 노드 생성과 추가

1. 요소 노드 생성

  • Document.prototype.createElement(tagName)메서드는 요소 노드를 생성하여 반환함
const $li = document.createElement('li');

2. 텍스트 노드 생성

  • Document.prototype.createTextNode(text)메서드는 텍스트 노드를 생성하여 반환함
const textNode = document.createTextNode('Banana');

3. 텍스트 노드를 요소 노드의 자식 노드로 추가

  • Node.prototype.appendChild(childNode)메서드는 매개변수 childNode에게 인수로 전달한 appendChild메서드를 호출한 노드의 마지막 자식 노드로 추가함
$li.appendChild(textNode);

4. 요소 노드를 DOM에 추가

  • Node.prototype.appendChild메서드를 사용하여 텍스트 노드와 부자관계로 연결한 요소 노드를 #fruits요소 노드의 마지막 자식 요소로 추가함
$fruits.appendChild($li);

39.6.4 복수의 노드 생성과 추가

  • 여러 개의 노드를 생성하여 DOM에 추가하면 리플로우와 리페인트가 여러번 실행되므로 이러한 문제를 회피하기 위해 DocumentFragment를 사용
...
const $fruits = document.getElementById('fruits');

// DocumentFragment 노드 생성
const $fragment = document.createDocumentFragment();

['Apple', 'Banana', 'Orange'].forEach(text => {
  const $li = document.createElement('li');
  const textNode = document.createTextNode(text);
  $li.appendChild(textNode);
  $fragment.appendChild($li);
  $fruits.appendChild($fragment);
})
...

39.6.5 노드 삽입

마지막 노드로 추가

  • Node.prototype.appendChild메서드는 인수로 전달받은 노드를 자신을 호추한 노드의 마지막 자식 노드로 DOM에 추가, 위치 지정할 수 없음

지정한 위치에 노드 삽입

  • Node.prototype.insertBefore(newNode, childNode)메서드는 첫번째 인수로 전달받은 노드를 두번째 인수로 전달받은 노드 앞에 삽입
$fruits.insertBefore($li, $fruits.lastElementChild);

39.6.6 노드 이동

  • DOM에 이미 존재하는 노드를 appendChild또는 insertBefore메서드를 사용하여 DOM에 다시 추가하면 노드가 이동함
// 현재 순서 : Apple - Banana - Orange
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

39.6.7 노드 복사

  • Node.prototype.cloneNode메서드는 노드의 사본을 생성하여 반환함
  • 매개변수 deep에 true를 전달하면 깊은복사, false를 전달하면 얕은복사 진행
const $deepClone = $fruits.cloneNode(true);

39.6.8 노드 교체

  • Node.prototype.replaceChild메서드는 자신이 호출한 노드의 자식 노드를 다른 노드로 교체함
$fruit.replaceChild($newChild, $fruits.firstElementChild);

39.6.9 노드 삭제

  • Node.prototype.removeChild(child)메서드는 child매개변수에 인수로 전달한 노드를 DOM에서 삭제함
$fruits.removeChild($fruits.lastElementChild);

39.7 어트리뷰트


39.7.1 어트리뷰트 노드와 attributes프로퍼티

  • 요소 노드의 모든 어트리뷰트 노드는 요소 노드의 Element.prototype.attributes프로퍼티로 취득할 수 있음
  • attributes프로퍼티는 읽기전용 프로퍼티며, NamedNodeMap객체를 반환

39.7.2 HTML 어트리뷰트 조작

  • Element.prototype.getAttribute/setAttribute메서드를 사용하면 요소 노드에서 직접 HTML어트리뷰트 값을 취득하거나 변경할 수 있음
...
// value 어트리뷰트 값을 취득
const inputValue = $input.getAttribute('value');
// value 어트리뷰트 값을 변경
$input.setAttribute('value', 'foo');
...
  • Element.prototype.hasAttribute(attributeName)메서드를 사용하여 특정 HTML어트리뷰트가 존재하는지 확인 가능
  • Element.prototype.removeAttribute메서드를 사용하여 특정 HTML어트리뷰트 삭제
...
if ($input.hasAttribute('value')) {
  $input.removeAttribute('value');
}
...

39.7.3 HTML어트리뷰트 vs DOM프로퍼티

  • HTML어트리뷰트의 역할은 HTML요소의 초기상태를 지정하는 것
  • DOM프로퍼티는 HTML어트리뷰트의 값을 초기값으로 가지고 있으며 참조와 변경 가능

1. 어트리뷰트 노드

  • HTML어트리뷰트로 지정한 HTML요소의 초기 상태는 어트리뷰트 노드에서 관리함

2. DOM프로퍼티

  • 사용자가 입력한 최신상태는 HTML어트리뷰트에 대응하는 요소 노드의 DOM프로퍼티가 관리함

3. HTML어트리뷰트와 DOM프로퍼티의 대응관계

  • 대부분의 HTML어트리뷰트는 HTML어트리뷰트와 동일한 DOM프로퍼티와 1:1로 대응하지만, 언제나 1:1대응인 것도 아니고 반드시 이름이 일치하는 것도 아님

4. DOM프로퍼티 값의 타입

  • getAttribute메서드로 취득한 어트리뷰트 값은 언제나 문자열임
  • DOM프로퍼티로 취득한 최신 상태값은 문자열이 아닐 수도 있음

5. data어트리뷰트와 dataset프로퍼티

  • data어트리뷰트와 dataset프로퍼티를 사용하면 HTML요소에 정의한 사용자 정의 어트리뷰트와 자바스크립트 간에 데이터를 교환할 수 있음

39.8 스타일


### 39.8.1 인라인 스타일 조작 - HTMLElement.prototype.style프로퍼티는 인라인 스타일을 취득하거나 추가, 변경함 ``` $div.style.color = 'blue'; // CSS프로퍼티는 케밥케이스를 따름 $div.style.backgroundColor = 'yellow'; // CSS프로퍼티를 그대로 사용하려면 대괄호 표기법을 사용 $div.style['background-color'] = 'yellow'; ```

39.8.2 클래스 조작

className

  • Element.prototype.className프로퍼티는 HTML요소의 class어트리뷰트 값을 취득하거나 변경함
  • 문자열을 반환하므로 공백으로 구분된 여러 개의 클래스를 반환하는 경우 다루기 불편함
$box.className = $box.className.replace('red', 'blue');

classList

  • Element.prototype.classList프로퍼티는 class어트리뷰트 정보를 담은 DOMTokenList객체를 반환함
console.log($classList);
// DOMTokenList(2) [length: 2, value: 'box blue', 0: 'box', 1: 'blue']
  • DOMTokenList객체의 메서드
    add : 인수로 전달한 1개 이상의 문자열을 class어트리뷰트 값으로 추가함
$box.classList.add('foo');
$box.classList.add('bar', 'baz');

remove : 인수로 전달한 1개 이상의 문자열과 일치하는 클래스를 class어트리뷰트에서 삭제함

$box.classList.remove('foo');
$box.classList.remove('bar', 'baz');

item(index) : 인수로 전달한 index에 해당하는 클래스를 class어트리뷰트에서 반환함

$box.classList.item(0); // -> 'box'

contains(className) : 인수로 전달한 문자열과 일치하는 클래스가 class어트리뷰트에 포함되어 있는지 확인

$box.classList.contains('box'); // -> true

replace(oldClassName, newClassName) : class어트리뷰트에서 첫번째 인수로 전달한 문자열을 두번째 인수로 전달한 문자열로 변경

$box.classList.replace('red', 'blue'); // -< class='box blue'

toggle(className[, force]) : class어트리뷰트에 인수로 전달한 문자열과 일치하는 클래스가 존재하면 제거, 존재하지 않으면 추가함

$box.classList.toggle('foo'); // -> class='box blue foo'
$box.classList.toggle('foo'); // -> class='box blue'

39.8.3 요소에 적용되어 있는 CSS스타일 참조

  • window.getComputedStyle(element[, pseudo])메서드는 첫번째 인수로 전달한 요소 노드에 적용되어 있는 스타일을 CSSStyleDeclaration객체에 담아 반환
const computedStyle = window.getComputedStyle($box);

console.log(computedStyle.width); // 100px
profile
항상 재밌는 뭔가를 찾고 있는 프론트엔드 개발자

0개의 댓글