DOM(Document Object Model)

DOM(Document Object Model)은 HTML이나 XML 문서의 구조를 표현하는 프로그래밍 인터페이스로, 웹 페이지를 프로그래밍 언어가 이해하고 조작할 수 있는 구조화된 표현이다.
HTML이나 XML 문서를 트리 구조로 표현하여, 각 요소를 노드(node)라는 객체로 다룰 수 있게 해준다.
이는 마치 가계도처럼, 부모-자식 관계로 문서의 구조를 표현한다.
W3C와 WHATWG에 의해 표준화되어 있다.

The Document Object Model used to access objects in web pages with eg. javascript
Source: https://ko.wikipedia.org/wiki/%EB%AC%B8%EC%84%9C_%EA%B0%9D%EC%B2%B4_%EB%AA%A8%EB%8D%B8#/media/%ED%8C%8C%EC%9D%BC:DOM-model.svg

DOM은 HTML, XML, SVG 문서의 프로그래밍 인터페이스로, 문서의 구조를 메모리에 트리 형태로 표현한다.
주요 목적은 다음과 같다:

  1. 웹 페이지를 스크립트나 프로그래밍 언어와 연결
  2. 문서 구조, 스타일, 내용을 동적으로 변경 가능
  3. 프로그래밍 언어에 문서 접근 및 조작 방법 제공

DOM의 장단점

장점:

  • 언어 및 플랫폼 독립적
  • 문서 구조를 쉽게 탐색 가능
  • 동적이고 사용자 정의 가능
  • 파일을 한 번만 파싱

단점:

  • 많은 RAM 사용
  • 대규모 문서 처리 시 속도 저하
  • 복잡한 문서의 경우 메모리 사용량 증가

DOM의 중요성

DOM은 웹 개발에서 핵심적인 역할을 한다:

  • 동적 웹 페이지 생성 가능
  • 클라이언트 측 변경 구현
  • 사용자 상호작용 처리

DOM의 구조와 노드 타입

DOM의 모든 객체는 Node의 한 종류이며, DOM 트리는 여러 종류의 노드로 구성된다:

  1. 문서 노드(Document Node): DOM 트리의 최상위에 위치하는 루트 노드이다. 웹 페이지 전체를 대표하며, 다른 모든 노드에 접근하기 위한 진입점 역할을 한다.

    1
    2
    3
    4
    5
    
    // Document 노드 사용 예시
    document.getElementById('myElement');         // 요소 검색
    document.createElement('div');                // 새 요소 생성
    document.createTextNode('Hello');            // 텍스트 노드 생성
    document.documentElement;                    // <html> 요소 접근
    
  2. 요소 노드(Element Node): HTML 요소를 표현하는 노드이다. 웹 페이지의 구조를 형성하는 기본 단위이며, 다른 노드들을 포함할 수 있다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    const element = document.querySelector('.myClass');
    
    // Element 노드의 주요 속성과 메서드
    element.tagName;                             // 태그 이름 확인
    element.children;                            // 자식 요소들 접근
    element.parentElement;                       // 부모 요소 접근
    element.nextElementSibling;                  // 다음 형제 요소
    element.previousElementSibling;              // 이전 형제 요소
    element.innerHTML;                          // HTML 내용 조작
    element.classList;                          // 클래스 목록 관리
    
  3. 텍스트 노드(Text Node): HTML 요소 내의 텍스트 내용을 표현한다.Element 노드의 자식으로 존재하며, 실제 콘텐츠를 저장한다.

    1
    2
    3
    4
    5
    6
    7
    
    // Text 노드 생성과 조작
    const textNode = document.createTextNode('Hello World');
    element.appendChild(textNode);               // 텍스트 노드 추가
    
    // 텍스트 내용 변경 방법
    element.textContent = 'New Text';            // 순수 텍스트로 변경
    element.innerText = 'Visible Text';          // 화면에 보이는 텍스트만
    
  4. 속성 노드(Attribute Node): HTML 요소의 속성을 나타낸다. Element 노드의 특성을 정의하는 데 사용된다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    // Attribute 노드 조작
    element.getAttribute('class');               // 속성 값 가져오기
    element.setAttribute('id', 'newId');         // 속성 설정하기
    element.hasAttribute('data-custom');         // 속성 존재 확인
    element.removeAttribute('style');            // 속성 제거하기
    
    // 데이터 속성 다루기
    element.dataset.customValue = 'hello';       // data-* 속성 설정
    console.log(element.dataset.customValue);    // data-* 속성 읽기
    
  5. 주석 노드(Comment Node): HTML 주석을 표현한다. 문서 구조에는 영향을 주지 않지만, 개발자들을 위한 정보를 포함할 수 있다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    // Comment 노드 생성과 사용
    const comment = document.createComment('이것은 주석입니다');
    element.appendChild(comment);
    
    // 주석 노드 찾기
    const comments = document.getElementsByTagName('*');
    for (let node of comments) {
        if (node.nodeType === 8) {              // 8은 주석 노드를 의미
            console.log('Found comment:', node.nodeValue);
        }
    }
    
  6. DocumentFragment Node: 경량화된 Document 객체로, 여러 노드를 그룹화하여 한 번에 DOM에 삽입할 때 사용된다. 성능 최적화에 매우 유용하다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    // DocumentFragment 사용 예시
    const fragment = document.createDocumentFragment();
    for (let i = 0; i < 1000; i++) {
        const element = document.createElement('div');
        element.textContent = `항목 ${i}`;
        fragment.appendChild(element);
    }
    // 한 번의 DOM 업데이트로 모든 요소 추가
    document.body.appendChild(fragment);
    
  7. DocumentType Node: 문서의 타입을 정의한다. HTML5의 DOCTYPE 선언을 표현한다.

    1
    2
    3
    
    // DocumentType 노드 접근
    console.log(document.doctype);               // <!DOCTYPE html>
    console.log(document.doctype.name);          // "html"
    

이러한 다양한 노드 타입들은 각각의 특성과 목적을 가지고 있으며, 이들이 서로 연결되어 하나의 완전한 문서 구조를 형성한다. 노드 간의 관계는 부모-자식-형제 관계로 표현되며, 이를 통해 문서의 계층 구조를 표현할 수 있다.

DOM 조작의 기본 작업

  1. 요소 선택하기:

    1
    2
    3
    4
    5
    
    // 다양한 방법으로 요소를 선택할 수 있습니다
    const byId = document.getElementById('myId');
    const byClass = document.getElementsByClassName('myClass');
    const byQuery = document.querySelector('.myClass #myId');
    const byQueryAll = document.querySelectorAll('div');
    
  2. 요소 생성과 추가:

    1
    2
    3
    4
    5
    6
    7
    8
    
    // 새로운 요소를 만들고 문서에 추가할 수 있습니다
    const newDiv = document.createElement('div');
    newDiv.textContent = '새로운 요소입니다';
    document.body.appendChild(newDiv);
    
    // 요소를 특정 위치에 삽입할 수도 있습니다
    const referenceElement = document.getElementById('reference');
    document.body.insertBefore(newDiv, referenceElement);
    
  3. 요소 수정하기:

    1
    2
    3
    4
    5
    
    // 요소의 내용이나 속성을 변경할 수 있습니다
    element.textContent = '새로운 텍스트';
    element.innerHTML = '<span>HTML 내용</span>';
    element.style.backgroundColor = 'blue';
    element.classList.add('new-class');
    
  4. 요소 삭제하기:

    1
    2
    3
    4
    
    // 요소를 문서에서 제거할 수 있습니다
    element.remove();
    // 또는
    element.parentNode.removeChild(element);
    

이벤트 처리

DOM은 사용자 상호작용을 처리하는 이벤트 시스템을 제공한다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 이벤트 리스너 추가하기
element.addEventListener('click', function(event) {
    console.log('요소가 클릭되었습니다!');
    // 이벤트 객체를 통해 추가 정보 얻기
    console.log('클릭된 좌표:', event.clientX, event.clientY);
});

// 이벤트 버블링과 캡처링
parent.addEventListener('click', function(e) {
    console.log('부모 요소 클릭');
    // 이벤트 전파 중단
    e.stopPropagation();
}, true); // true는 캡처링 단계에서 실행

실제 활용 예시

다음은 DOM을 활용한 실제 웹 애플리케이션의 예시:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 동적인 리스트 만들기
function createTodoList() {
    const todoList = document.createElement('ul');
    const todos = ['할 일 1', '할 일 2', '할 일 3'];
    
    todos.forEach(todo => {
        const li = document.createElement('li');
        li.textContent = todo;
        
        // 삭제 버튼 추가
        const deleteButton = document.createElement('button');
        deleteButton.textContent = '삭제';
        deleteButton.addEventListener('click', () => {
            li.remove();
        });
        
        li.appendChild(deleteButton);
        todoList.appendChild(li);
    });
    
    document.body.appendChild(todoList);
}

DOM의 성능 고려사항

DOM 조작은 비용이 많이 드는 작업일 수 있다.
따라서 다음과 같은 최적화 기법을 고려해야 한다:

  1. 문서 프래그먼트 사용:

    1
    2
    3
    4
    5
    6
    
    const fragment = document.createDocumentFragment();
    for (let i = 0; i < 1000; i++) {
        const element = document.createElement('div');
        fragment.appendChild(element);
    }
    document.body.appendChild(fragment); // 한 번의 리플로우만 발생
    
  2. 캐싱과 최소화:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    // 잘못된 방법
    for (let i = 0; i < 1000; i++) {
        document.getElementById('myElement').style.top = i + 'px';
    }
    
    // 개선된 방법
    const element = document.getElementById('myElement');
    for (let i = 0; i < 1000; i++) {
        element.style.top = i + 'px';
    }
    

Virtual DOM

Virtual DOM(가상 DOM)은 웹 애플리케이션의 성능을 최적화하기 위해 사용되는 프로그래밍 개념이다.
실제 DOM의 추상화된 복사본으로, 메모리 상에 존재하는 JavaScript 객체이다.
웹 페이지의 UI를 효율적으로 업데이트하기 위해 사용되며, 주요 목적은 DOM 조작으로 인한 성능 저하를 최소화하는 것이다.

1
2
3
4
<div id="app">
  <h1>제목</h1>
  <p>문단입니다</p>
</div>

이 HTML은 다음과 같은 DOM 트리를 형성한다:

1
2
3
4
5
div
├── h1
│   └── "제목"
└── p
    └── "문단입니다"

위의 HTML은 Virtual DOM에서 다음과 같이 표현될 수 있다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  type: 'div',
  props: {
    id: 'app'
  },
  children: [
    {
      type: 'h1',
      props: {},
      children: ['제목']
    },
    {
      type: 'p',
      props: {},
      children: ['문단입니다']
    }
  ]
}

작동 방식

Virtual DOM이 작동하는 과정을 단계별로 살펴보자:

  1. 초기 렌더링:

    • 처음 페이지가 로드될 때 전체 Virtual DOM이 생성된다.
    • 이 Virtual DOM을 기반으로 실제 DOM이 생성된다.
  2. 상태 변경 발생:

    1
    2
    
    // React에서의 상태 변경 예시
    setState({ text: '새로운 문단입니다' });
    
  3. Virtual DOM 업데이트:

    • 새로운 Virtual DOM 트리가 생성된다.
    • 이전 Virtual DOM과 새로운 Virtual DOM을 비교한다.
  4. 차이점 계산 (Diffing):

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    // 가상의 Diffing 알고리즘 예시
    function diff(oldNode, newNode) {
      if (!oldNode) {
        return { type: 'CREATE', newNode };
      }
      if (!newNode) {
        return { type: 'REMOVE' };
      }
      if (oldNode.type !== newNode.type) {
        return { type: 'REPLACE', newNode };
      }
      if (oldNode.props !== newNode.props) {
        return { type: 'UPDATE', props: newNode.props };
      }
      // 자식 노드들에 대해서도 재귀적으로 비교
    }
    
  5. 실제 DOM 업데이트:

    • 계산된 차이점만을 실제 DOM에 적용한다.
    • 이를 ‘재조정(Reconciliation)‘이라고 한다.

Virtual DOM의 이점

  1. 성능 최적화:

    • 실제 DOM 조작은 비용이 많이 든다.
    • Virtual DOM은 메모리 상에서 작업하므로 더 빠르다.
    • 여러 변경사항을 한 번에 처리할 수 있다.
  2. 크로스 플랫폼:

    • Virtual DOM은 실제 DOM에 종속되지 않는다.
    • 따라서 웹 외의 플랫폼(예: React Native)에서도 사용 가능하다.
  3. 선언적 프로그래밍:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    // 명령형 프로그래밍 (직접 DOM 조작)
    const element = document.getElementById('message');
    element.innerHTML = '안녕하세요';
    element.className = 'highlight';
    
    // 선언적 프로그래밍 (Virtual DOM 사용)
    function Message() {
      return <div className="highlight">안녕하세요</div>;
    }
    

실제 사용 예시

React에서 Virtual DOM을 활용하는 간단한 예시:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function TodoList() {
  const [todos, setTodos] = useState([]);

  const addTodo = (text) => {
    // 상태가 변경되면 Virtual DOM이 새로 생성됩니다
    setTodos([...todos, { id: Date.now(), text }]);
  };

  return (
    <div>
      {/* Virtual DOM은 이 부분의 변경사항을 효율적으로 처리합니다 */}
      {todos.map(todo => (
        <div key={todo.id}>{todo.text}</div>
      ))}
    </div>
  );
}

주의사항과 고려사항

  1. 항상 더 빠른 것은 아니다:

    • 작은 규모의 DOM 조작에서는 오히려 오버헤드가 될 수 있다.
    • Virtual DOM의 비교 작업도 비용이 발생한다.
  2. 메모리 사용:

    • Virtual DOM은 추가적인 메모리를 사용한다.
    • 매우 큰 애플리케이션에서는 이를 고려해야 한다.
  3. 최적화의 필요성:

    1
    2
    3
    4
    
    // 불필요한 재렌더링을 막기 위한 최적화
    const MemoizedComponent = React.memo(function MyComponent(props) {
      return <div>{props.value}</div>;
    });
    

Virtual DOM vs. 실제 DOM

비교 기준실제 DOM (Document Object Model)Virtual DOM
정의웹 페이지를 프로그래밍적으로 표현한 실제 문서 객체 모델실제 DOM의 가벼운 복사본으로, 메모리에 존재하는 가상의 DOM 표현
데이터 구조HTML 요소들의 트리 구조JavaScript 객체로 표현된 트리 구조
메모리 사용더 많은 메모리 사용 (각 노드가 많은 속성과 메서드를 포함)상대적으로 적은 메모리 사용 (필수 속성만 포함)
업데이트 방식직접적인 DOM 조작으로 즉시 화면에 반영변경사항을 모아서 최적화된 방식으로 한 번에 실제 DOM에 적용
업데이트 비용각각의 변경이 리플로우/리페인트를 발생시켜 비용이 큼메모리상에서 먼저 처리되어 실제 DOM 변경을 최소화
메모리 사용상대적으로 무거움실제 DOM보다 가벼움
조작 속도직접 조작시 느림 (특히 여러 변경사항이 있을 때)빠름 (변경사항을 배치로 처리)
조작 방법JavaScript를 통해 직접 조작 가능프레임워크(예: React)를 통해 간접적으로 조작
구현 예시javascript document.getElementById('app').innerHTML = 'Hello'javascript const vNode = { type: 'div', props: { id: 'app' }, children: ['Hello'] }
플랫폼 의존성브라우저 환경에 종속적플랫폼 독립적 (React Native 등에서 활용 가능)
이벤트 처리각 노드마다 이벤트 리스너 부착 가능루트 노드에만 이벤트 리스너 부착
상태 관리상태 변화를 직접 추적하기 어려움상태 변화를 추적하고 관리하기 용이
리렌더링 범위변경된 요소와 그 하위 요소 전체를 리렌더링실제 변경이 필요한 부분만 선택적으로 리렌더링
디버깅브라우저 개발자 도구로 직접 확인 가능특별한 개발자 도구 필요 (예: React DevTools)
최적화 방법수동으로 DOM 조작을 최적화해야 함diff 알고리즘을 통한 자동 최적화
사용 사례간단한 정적 페이지나 최소한의 인터랙션이 필요한 경우동적이고 복잡한 사용자 인터페이스가 필요한 경우
장점- 직접적인 조작 가능
- 간단한 변경에 적합
- 추가 레이어 없음
- 효율적인 업데이트
- 성능 최적화
- 선언적 프로그래밍 가능
단점- 잦은 업데이트시 성능 저하
- 복잡한 상태 관리 어려움
- 크로스 플랫폼 지원 어려움
- 추가적인 메모리 사용
- 초기 설정 필요
- 간단한 작업에 오버헤드 발생

사용 사례

React, Vue.js 등의 현대적인 JavaScript 프레임워크에서 Virtual DOM을 활용하여 UI 렌더링 성능을 최적화한다.


참고 및 출처

DOM 소개 - Web API | MDN