DOM
DOM(Document Object Model)은 브라우저가 HTML 문서를 파싱하여 만드는 트리 구조의 객체 모델입니다. 자바스크립트는 DOM API를 통해 문서의 요소를 조회하고, 추가하고, 수정하고, 삭제할 수 있으며, 이 변경이 화면에 바로 반영됩니다. 웹 페이지가 정적 문서에서 동적 애플리케이션으로 전환되는 기반입니다.
▶아키텍처 다이어그램
점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다
HTML은 서버에서 한 번 만들어져 브라우저에 도착한 정적 텍스트입니다. 사용자가 버튼을 클릭하거나, 서버에서 새 데이터가 오거나, 입력 폼의 유효성을 즉시 보여줘야 하는 상황에서 HTML 텍스트를 직접 편집할 방법은 없습니다. 페이지 전체를 다시 서버에서 받아 오는 방법이 있지만, 댓글 하나를 추가할 때마다 전체 페이지가 새로고침되면 사용성이 무너집니다. 자바스크립트가 화면의 특정 부분만 바꾸려면, 문서의 구조를 프로그래밍 언어가 이해하고 조작할 수 있는 형태로 갖고 있어야 합니다. DOM은 HTML 텍스트를 객체 트리로 바꿔서, 프로그램이 문서의 어디든 접근하고 수정할 수 있게 만든 인터페이스입니다.
1990년대 중반 웹 초기에는 페이지가 서버에서 완성된 HTML을 받아 그대로 보여주는 것이 전부였습니다. 사용자와의 상호작용이라고 해봐야 링크를 클릭해 다음 페이지로 이동하는 정도였습니다. 넷스케이프가 자바스크립트를 도입하면서 폼 검증이나 간단한 시각 효과가 가능해졌지만, 브라우저마다 문서 접근 방식이 달랐습니다. Netscape의 document.layers와 IE의 document.all은 서로 호환되지 않았고, 같은 기능을 두 번 작성해야 했습니다. W3C가 1998년 DOM Level 1을 표준으로 확정한 것은 이 호환성 문제를 해결하기 위해서였습니다. 모든 브라우저가 같은 API로 같은 트리 구조에 접근할 수 있게 되자, 자바스크립트로 본격적인 동적 웹이 가능해졌습니다. AJAX가 등장하고, jQuery가 DOM 조작을 단순화하고, 이후 React와 Vue 같은 프레임워크가 DOM 업데이트를 추상화한 흐름은 모두 이 표준 DOM 위에서 진행됐습니다.
브라우저가 HTML을 받으면 토큰화(tokenization)부터 시작합니다. <div>, <p>, 텍스트 같은 조각을 식별하고, 이 토큰들을 부모-자식 관계로 연결해 트리를 만듭니다. 이것이 DOM 트리입니다. document 객체가 루트이고, 그 아래에 html, head, body, 각 요소들이 노드로 매달립니다. 자바스크립트는 DOM API를 통해 이 트리를 조작합니다. document.querySelector('.item')로 노드를 찾고, element.textContent = '새 내용'으로 텍스트를 바꾸고, parentNode.appendChild(newNode)로 새 요소를 끼워 넣습니다. 이벤트도 DOM 트리를 따라 흐릅니다. 사용자가 버튼을 클릭하면, 이벤트가 루트에서 타겟까지 내려가고(캡처링), 타겟에서 처리되고, 다시 루트까지 올라갑니다(버블링). DOM이 바뀌면 브라우저는 변경된 부분의 레이아웃을 다시 계산하고(reflow), 화면을 다시 그립니다(repaint). 이 과정이 빈번하면 성능에 영향을 줍니다. 요소 100개를 하나씩 추가하면 reflow가 100번 발생할 수 있지만, DocumentFragment에 모아서 한 번에 추가하면 reflow를 줄일 수 있습니다. React 같은 프레임워크가 Virtual DOM을 쓰는 이유도 실제 DOM 변경 횟수를 최소화하기 위해서입니다.
DOM과 Virtual DOM은 혼동하기 쉽지만 계층이 다릅니다. DOM은 브라우저가 관리하는 실제 문서 트리이고, 모든 변경이 곧바로 렌더링 파이프라인을 거칩니다. Virtual DOM은 React 같은 프레임워크가 메모리에 유지하는 가벼운 자바스크립트 객체 트리입니다. 상태가 바뀌면 프레임워크가 새 Virtual DOM을 만들어 이전 것과 비교(diffing)하고, 차이가 나는 부분만 실제 DOM에 반영합니다. Virtual DOM이 있다고 해서 DOM이 필요 없어지는 것은 아닙니다. Virtual DOM은 DOM 업데이트를 효율적으로 모아서(batching) 처리하는 전략이지, DOM을 대체하는 것은 아닙니다. 최종적으로 화면에 반영되려면 반드시 실제 DOM이 바뀌어야 합니다. DOM과 HTML도 구분해야 합니다. HTML은 문서의 초기 상태를 기술한 텍스트이고, DOM은 브라우저가 그 텍스트를 해석해 만든 라이브 객체입니다. 자바스크립트로 DOM을 바꿔도 원본 HTML 파일은 변하지 않습니다. 개발자 도구에서 보이는 것은 HTML이 아니라 현재 시점의 DOM 상태입니다.
DOM 조작은 모든 프론트엔드 개발의 기초 동작입니다. 프레임워크를 쓰든 바닐라 자바스크립트를 쓰든, 결국 화면에 변화를 만드는 것은 DOM을 바꾸는 행위입니다. React의 JSX가 컴포넌트를 선언적으로 기술하더라도, 그 결과는 실제 DOM 노드의 생성과 업데이트로 이어집니다. 성능 문제를 만나면 대부분 DOM 조작 패턴으로 거슬러 올라갑니다. 리스트 항목 1,000개를 한꺼번에 DOM에 추가하면 렌더링이 멈추고, 스크롤할 때마다 요소 크기를 읽고 쓰기를 반복하면 레이아웃 스래싱(layout thrashing)이 발생합니다. 이럴 때 가상 스크롤(virtual scrolling)로 화면에 보이는 항목만 DOM에 유지하거나, 읽기와 쓰기를 분리해서 배치하는 것이 대응 방법입니다. 디버깅에서도 DOM 이해가 핵심입니다. 개발자 도구의 Elements 탭은 현재 DOM의 라이브 상태를 보여주고, 노드를 직접 편집해 변경 결과를 즉시 확인할 수 있습니다. 이벤트가 예상대로 동작하지 않을 때 이벤트 리스너 탭에서 어떤 노드에 어떤 핸들러가 걸려 있는지 확인하는 것이 디버깅의 출발점입니다.
더 깊게 보기
현재 페이지의 개념 설명을 본 뒤 공식 문서로 바로 이동합니다.