DOM

김수정·2020년 5월 15일
0

브라우저 Javascript

목록 보기
2/9

DOM트리

DOM은 자바스크립트로 웹페이지(html)를 제어할 때 쓰는 객체입니다.
이 객체는 트리형태로 구조화할 수 있습니다. 객체가 그러하듯이요.
웹페이지의 모든 요소가 어떻게 document객체 안에 들어가는지 봅시다.

DOM을 이루는 구성요소들을 node라고 부릅니다. node엔 여러 종류가 있겠죠. 총 12가지이지만, 주로 사용하는 것들만 보겠습니다.

  • element node(요소 노드): html tag들입니다. 그 중 <html>은 root node입니다.

  • text node(텍스트 노드): 문자열만 담는 노드로 요소 노드의 자식이 될 순 있지만 자신이 자식을 가질 순 없습니다. 새 줄이나 공백은 유효한 문자입니다. 따라서 우리가 보기 편하게 작성하느라 줄바꿈하는 모든 것이 텍스트 노드로 잡힙니다.

    텍스트 노드 생성의 두 가지 예외
    1) 역사적인 이유로 <html><head> 사이의 공백과 새 줄은 무시됩니다.
    2) html 명세서에서 모든 콘텐츠가 body 안쪽에 있어야 한다고 하여 </body>뒤에 무언가 넣더라도 자동으로 body 안쪽으로 옮겨집니다. 따라서 그 뒤에 공백이 있을 수 없습니다.

  • comment node(주석 노드): 주석

  • document node(문서 노드): DOM의 진입점

자동교정
우리의 웹페이지가 DOM구성을 똑바로 하지 못했더라도 어느정도 브라우저가 자동으로 매꿔줍니다. 그렇다고 막 썼다간 브라우저가 계산해서 넣어줘야 하는 것도 많아지고, 브라우저마저 잘못 해석할 경우 알 수 없는 에러가 발생하겠죠?

  • 최상위 태그로 <html>이 자동 생성됩니다.
  • 닫는 태그가 없으면 닫아줍니다.
  • table에서 tbody가 자동으로 생깁니다.

DOM 탐색하기

DOM 접근

상단 노드
<html> = document.documentElement
<body> = document.body
<head> = document.head


자식/후손 노드
텍스트 노드를 포함한 모든 자식 노드에 접근하는 방법입니다.

descendants(후소노드) = document.<elem>.childNodes
firstChild, lastChild프로퍼티를 이용하여 첫 번째, 마지막 자식노드에 빠르게 접근할 수 있습니다.
자식 노드의 존재 여부를 검사할 땐 elem.hasChildNodes()

const elem = document.body;
elem.firstChild === elem.childNodes[0] // true
elem.lastChild === elem.childNodes[elem.childNodes.length - 1]

childNodes는 유사 배열 객체인 컬렉션(collection)입니다.
따라서 몇가지 특징이 있습니다.
이터러블이기 때문에 for..of문을 사용할 수 있지만 배열은 아니므로 배열 메서드를 쓸 수 없습니다. 배열 메서드를 쓰기 위해서 Array.from을 사용할 수 있습니다.
for..in문은 사용하지 않습니다. 컬렉션에서 거의 사용되지 않는 추가 프로퍼티까지 순회 대상에 들어가기 때문입니다.


형제 노드
같은 부모를 가진 노드는 sibling node(형제 노드)입니다.
다음 형제 = document.<elem>.nextSibling
이전 형제 = document.<elem>.previousSibling
부모 노드 = document.<elem>.parentNode

// <body>의 부모 노드는 <html>입니다
alert( document.body.parentNode === document.documentElement ); // true

// <head>의 다음 형제 노드는 <body>입니다.
alert( document.head.nextSibling ); // HTMLBodyElement

// <body>의 이전 형제 노드는 <head>입니다.
alert( document.body.previousSibling ); // HTMLHeadElement

요소 접근
노드는 html의 모든 것들을 선택할 수 있지만, 우리가 주로 탐색할 것들은 element node들이죠. 그래서 이들을 조작하는 방법을 좀 더 알아봅시다.

부모 요소 노드 = document.<elem>.parentElement
형제 요소 노드 = document.<elem>.previousElementSibling, document.<elem>.nextElementSibling
자식 요소 노드 = document.<elem>.children, document.<elem>.firstElementChild, document.<elem>.lastElementChild

DOM 검색

document.getElementById('id_name')
요소에 id속성이 있다면 id_name을 이용하여 바로 접근할 수 있습니다.
id 속성값 그대로 변수로 써도 먹히지만, 동일한 이름을 가진 변수가 선언되면 무용지물이 되므로 사용하지 않길 바랍니다.

<div id="elem">
  <div id="elem-content">Element</div>
</div>

<script>
  // 요소 얻기
  let elem = document.getElementById('elem');

  // 배경색 변경하기
  elem.style.background = 'red';
</script>

querySelectorAll('css selector')
css선택자를 활용하여 해당 요소를 전부 찾아주기 때문에 매우 편리합니다. 가상 클래스 등도 다 선택 가능합니다.
document.querySelectorAll('css selector') or elem.querySelectorAll('css selector')

querySelector('css selector')
css 선택자에 대응하는 요소 중 첫 번째 요소만 반환합니다.

elem.matches('css selector')
elem이 주어진 css 선택자와 일치하는지 판단해 줍니다. true/false

<a href="http://example.com/file.zip">...</a>
<a href="http://ya.ru">...</a>

<script>
  // document.body.children가 아니더라도 컬렉션이라면 이 메서드를 적용할 수 있습니다.
  for (let elem of document.body.children) {
    if (elem.matches('a[href$="zip"]')) {
      alert("주어진 CSS 선택자와 일치하는 요소: " + elem.href );
    }
  }
</script>

elem.closest('css selector')
elem 자기 자신을 포함하여 해당 css를 가지고 있는 가장 가까운 부모를 찾아줍니다.

getElementsByTagName(tag)
태그를 찾아줍니다. *을 넣으면 모든 태그가 검색됩니다.

getElementsByClassName(className)
class이름으로 찾습니다.

getElementsByName(name)
name 속성값으로 찾습니다.

DOM 노드 클래스

노드의 종류에 따라 지원하는 프로퍼티가 다르겠죠. 우선 클래스의 구조를 보도록 합시다.

EventTarget
루트에 있는 추상 클래스. DOM노드의 베이스 역할

Node
추상 클래스. 트리 탐색 기능을 제공하고, 추상클래스이므로 생성되진 않지만 이 클래스를 상속받는 클래스들이 있습니다.
트리 탐색 기능 - parentNode, nextSibling, childNodes
상속받는 클래스 - Element class, Text class, Comment class

Element
브라우저가 제공하는 xml, svg엘리먼트와 html요소 클래스의 베이스 역할을 합니다. 요소 전용 탐색 기능, 요소 전용 검색 기능을 제공합니다.
요소전용 탐색기능 - nextElementSibling, children ...
요소전용 검색기능 - getElementsByTagName, querySelector ...

HTMLElement
HTML 태그들과 매칭되는 클래스.

DOM노드 프로퍼티

개발자도구 element > Properties에서 해당 노드에 연결된 프로퍼티를 전부 확인할 수 있습니다.

nodeType
DOM 노드의 타입을 알아낼 때 사용합니다.
ex) elem.nodeType == 1 // 요소노드 3:텍스트노드, 9:문서객체

nodeName, tagName
DOM노드의 태그 이름을 알아낼 수 있습니다. nodeName은 모든 노드에 지원되지만 tagName은 요소 노드에만 지원됩니다.

console.log(document.body.nodeName); // BODY
console.log(document.body.tagName); // BODY

innerHTML
요소 안의 HTML을 문자열 형태로 받아올 수 있고, 수정하는 것도 가능합니다.

주의점
innerHTML안에 스크립트를 넣으면 HTML에 들어가긴 하지만 실행은 되지 않습니다.
innerHTML+="추가 html code..." 방식으로 할 경우 이건 추가가 아니라 내용을 처음부터 새로 덮어씁니다.
따라서 리소스도 낭비되고 기존 내용이 유지가 되지 않습니다.

outerHTML
자기 자신까지 포함하여 계산하고, 부모가 없으면 바뀌지 않습니다. 요소 자체를 바꾸진 않고 안의 요소를 교체합니다.

nodeValue, data
요소 노드가 아닌 다른 타입의 노드를 읽음

<body>
  안녕하세요.
  <!-- 주석 -->
  <script>
    let text = document.body.firstChild;
    alert(text.data); // 안녕하세요.

    let comment = text.nextSibling;
    alert(comment.data); // 주석
  </script>
</body>

textContent
태그는 제외하고 오로지 텍스트만 가져올 수 있습니다.
모든지 텍스트로 인식합니다.

<div id="elem1"></div>
<div id="elem2"></div>

<script>
  let name = prompt("이름을 알려주세요.", "<b>곰돌이 푸!</b>");

  elem1.innerHTML = name;
  elem2.textContent = name;
</script>

hidden
돔 요소를 보여줄지 여부를 결정. html, js에서 다 사용 가능합니다. display: none;과 같음

<div>아래 두 div를 숨겨봅시다.</div>

<div hidden>HTML의 'hidden' 속성 사용하기</div>
<div id="elem">자바스크립트의 'hidden' 프로퍼티 사용하기</div>

<script>
  elem.hidden = true;
</script>

프로퍼티 조작하기
위에 공부한 내장 돔 프로퍼티 말고 새로 만들어서 사용할 수도 있습니다.

// 프로퍼티 추가
document.body.myData = {
  name: 'Caesar',
  title: 'Imperator'
};

alert(document.body.myData.title); // Imperator

// 메서드 추가
document.body.sayTagName = function() {
  alert(this.tagName);
};

document.body.sayTagName(); // BODY (sayTagName의 'this'엔 document.body가 저장됩니다.)

html속성

html의 표준 속성은 Dom 프로퍼티가 됩니다.
html 속성은 대소문자를 가리지 않고 항상 문자열입니다.
하지만 DOM 프로퍼티는 대소문자를 구별하며 항상 문자열은 아닙니다.

html 속성 제어하기

elem.hasAttribute(name)
속성의 존재여부 확인

elem.getAttribute(name)
속성값을 가져옴

elem.setAttribute(name, value)
속성값을 변경함

elem.removeAttribute(name)
속성값을 지움

elem.attributes
모든 속성값을 읽음. 컬렉션을 반환함

<body>
  <div id="elem" about="Elephant"></div>

  <script>
    alert( elem.getAttribute('About') ); // (1) 'Elephant', 속성 읽기

    elem.setAttribute('Test', 123); // (2) 속성 추가하기

    alert( elem.outerHTML ); // (3) 추가된 속성 확인하기

    for (let attr of elem.attributes) { // (4) 속성 전체 나열하기
      alert( `${attr.name} = ${attr.value}` );
    }
  </script>
</body>
<a id="a" href="#hello">link</a>
<script>
  // 속성
  alert(a.getAttribute('href')); // #hello

  // 프로퍼티
  alert(a.href ); // 실행되는 사이트 주소에 따라 http://site.com/page#hello 형태로 값이 저장됨
</script>

비표준 속성, dataset

클래스를 넣고 빼는 것보다 속성은 제어가 쉬우므로 속성으로 사용하는 것을 권장합니다.
임의의 속성을 우리가 만들어서 사용해도 되지만, 만일 그 속성이 나중에 표준속성명이 될 수도 있으니 안전하게 만들어 둔 dataset을 사용합니다.

<style>
  .order[data-order-state="new"] {
    color: green;
  }

  .order[data-order-state="pending"] {
    color: blue;
  }

  .order[data-order-state="canceled"] {
    color: red;
  }
</style>

<div id="order" class="order" data-order-state="new">
  A new order.
</div>

<script>
  // 읽기
  alert(order.dataset.orderState); // new

  // 수정하기
  order.dataset.orderState = "pending"; // (*)
</script>

요소 생성하기

document.createElement(tag)
태그이름으로 새로운 요소를 만듦

document.createTextNode(text)
텍스트를 이용해 새로운 텍스트 노드를 만듦

let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>안녕하세요!</strong> 중요 메시지를 확인하셨습니다.";

문서에 삽입하기

모든 노드 삽입 메서드는 자동으로 기존에 있던 노드를 삭제하고 새로운 곳으로 노드를 옮깁니다.

node.append(...nodes or strings)
노드나 문자열을 node 끝에 삽입합니다.

node.prepend(...nodes or strings)
노드나 문자열을 node 맨 앞에 삽입합니다.

node.before(...nodes or strings)
노드나 문자열을 node 이전에 삽입합니다.

node.after(...nodes or strings)
노드나 문자열을 node 다음에 삽입합니다.

node.replaceWith(...nodes or strings)
node를 새로운 노드나 문자열로 대체합니다.

insertAdjacentHTML/Text/Element
위의 메서드들은 삽인한 것을 문자열로 인식하고, 이 메서드는 html로 인식합니다.
elem.insertAdjacentHTML(where, html)에서 첫 번째 매개변수는 elem을 기준으로 하는 상대 위치로, 반드시 다음 값 중 하나여야 합니다.
"beforebegin" – elem 바로 앞에 html을 삽입합니다,
"afterbegin" – elem의 첫 번째 자식 요소 바로 앞에 html을 삽입합니다.
"beforeend" – elem의 마지막 자식 요소 바로 다음에 html을 삽입합니다.
"afterend" – elem 바로 다음에 html을 삽입합니다.
두 번째 매개변수는 HTML 문자열로, 이 HTML은 “그 자체로” 삽입됩니다.

elem.insertAdjacentText(where, text) – insertAdjacentHTML과 같은 문법인데, HTML 대신 text를 ‘문자 그대로’ 삽입한다는 점이 다릅니다,
elem.insertAdjacentElement(where, elem) – 역시 같은 문법인데, 요소를 삽입한다는 점이 다릅니다.

노드 삭제하기

node.remove()

노드 복제하기

elem.cloneNode(boolean)
true를 호출하면 elem의 깊은 복사, false를 호출하면 elem만 복제.

documentFragment

여러 노드로 구성된 그룹을 감싸 다른 곳으로 전달하게 해주는 래퍼.
documentFragment에 다른 노드를 넣으면 fragment는 사라지고 그 안에 있던 노드만 남습니다.

deprecated methods

parentElem.appendChild(node)
parentElem의 마지막 자식으로 node를 추가함

parentElem.insertBefore(node, nextSibling)
node를 parentElem 안의 nextSibling앞에 추가함

parentElem.replaceChild(node, oldChild)
parentElem의 자식 노드 중 oldChild를 node로 교체함

parentElem.removeChild(node)
parentElem에서 node를 삭제함

document.write

페이지를 불러오는 도중에 동작하지만 돔을 다시 그리진 않아서 빠름.

profile
정리하는 개발자

0개의 댓글