HTML 트리와 DOM api
2025/02/25
n°60
category : JavaScript
☼
2025/02/25
n°60
category : JavaScript
HTML는 정적 트리이고 DOM api는 동적 트리이다.
문제는 구조에서 오는 복잡성 : 참조 무결성, 실행 순서
해결은 복잡성을 줄이기 위한 전략
리액트와 같은 프론트엔드 라이브러리나 프레임워크는 왜 사용하나요?
면접에서 이런 질문을 받았다. 난감한 기분이 드는 걸 보니, 그동안 너무 당연하게만 생각했던 것 같다. 현대 웹 애플리케이션의 복잡한 UX를 구현하는데에 기존 DOM api가 적합하지 않기 때문이라고 답했다. 성능 최적화, 코드 복잡성 등을 얘기했지만 핵심을 놓쳤다는 느낌이었다.
DOM API는 왜 복잡성을 증가시키는가?
최근 (다시) 공부 중인 자료 구조를 기반으로 HTML, DOM api를 이해하고, 그 질문에 다시 답해본다.
HTML의 특징은 다음과 같다.
HTML 자료 구조: 그래프 > 비순환 방향 그래프 (DAG) 방향 (루트에서 노드로) > 트리 > 계층적 정적 트리
서버에서 전달된 HTML가 로드되면 브라우저가 이를 읽고 해석한다.
브라우저에서는 HTML를 바탕으로 DOM 트리, CSSOM 트리를 구성한다.
이렇게 구성된 두 트리를 결합하여 리플로우와 리페인팅으로 HTML 렌더링이 이루어진다.
그러나 HTML 자체는 변경이 없다. DOM 조작이 일어나지 않는 한.
서버가 전송한 HTML은 노드 관계를 정의한 그래프(트리)이다.
이 트리는 계층적으로 구성되며, 루트 노드는 <html>이다.
서버에 저장된 원본 html의 트리는 루트와 리프까지 계층 관계는 변하지 않는다. 즉 정적이다.
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM 조작 예제</title>
<script defer src="./script.js"></script>
</head>
<body>
<div id="container">
<button id="changeText">텍스트 변경</button>
<p class="text">기본 텍스트</p>
</div>
</body>
</html>DOM 트리는 브라우저가 구성한다. JS는 DOM 트리에 접근하여 변경할 수 있는 인터페이스인 DOM api를 제공한다. document 일급 객체를 통해.
DOM 트리 : 브라우저가 HTML을 파싱하여 구성한 노드의 트리 구조
DOM api (document) : 브라우저가 구성한 DOM 트리를 조회 및 변경하기 위한 인터페이스
DOM 객체 자료 구조: 그래프 > 양방향 그래프 > 동적 트리 > 계층적 동적 트리
JS로 HTML을 조회하고, 조작하기 위한 Web API이다.
서버에서 HTML이 브라우저로 전달되면 이를 바탕으로 document 전역객체를 만든다.
document는 HTML 노드(태그)의 계층 그래프를 객체 형태로 표현한다.
**HTML과 달리, DOM api에서 루트 노드는 document이다.
서버에 저장된 원본 HTML과 브라우저의 DOM 트리는 DOM 트리 변경 시 일치하지 않는다.
브라우저에서 실행된 스크립트로 브라우저가 구성한 DOM 트리가 변경된 것이니, 어떻게 보면 당연하다. 하지만 서버사이드 렌더링, 서버 컴포넌트 분리 등 서버와 브라우저의 분리된 실행 환경을 고려하지 않을 수 없다.
DOM 트리 생성 과정은 다음과 같다.
브라우저는 HTML의 문자를 파싱한다
토큰으로 만든다 (토크나이즈)
토큰으로 document 객체를 생성
브라우저에서 실행되는 JS 스크립트는 브라우저가 생성한 document 객체에 접근할 수 있다.
DOM 탐색: dom은 HTML 노드를 탐색 및 조회할 수 있는 기능을 제공한다.
DOM 조작: 탐색으로 가져온 노드의 HTML attribute을 재할당하여 HTML을 변경한다.
document는 전역 객체이므로, 자식 노드에서 부모 노드에 접근할 수 있고, 변경할 수 있다.
DOM에서 루트 노드는 document이며, html 태그는 그 하위 요소이다.
// 버튼 클릭 시 텍스트 변경
const button = document.getElementById("changeText");
button.addEventListener("click", () => {
const text = document.querySelector(".text");
text.textContent = "텍스트가 변경되었습니다!";
});document 객체를 통해 HTML 요소를 탐색하고 조작할 수 있다.
document의 메서드로 html에 접근할 수 있다. (getElementById, getElementById, querySelector, querySelectorAll, getElementsByClassName, getElementsByTagName 등)
탐색한 html 태그에 새로운 데이터를 재할당하여 DOM 트리를 변경할 수 있다.
속성이나 내용이 변경되면, 브라우저는 즉시 이를 감지한다.
브라우저는 이를 바탕으로 DOM 트리를 재구성하고 CSSOM과 결합하여 렌더링 과정(리플로우, 리페인트)을 수행한다.
DOM 트리가 변경되는 시점은 함수 실행 시점에 따라 다를 수 있다.
초기 스크립트 실행, 이벤트, api 요청, 타이머 등 다른 시점에 DOM 트리가 변경될 수 있다.
여기서 든 의문은 두가지이다.
document 객체의 루트 노드는 document 객체 자체인데, HTML의 루트는 <html>이다. 그럼 document와 HTML는 다른 구조의 트리가 아닌가?
document로 DOM 트리로 변경되면 변경 이전에 데이터는 어떻게 되지?
결론: HTML 트리는 계층적 정적 트리로 부모-자식 관계가 고정되지만, DOM API는 이를 변경 가능한 그래프로 다룬다. 이로 인해 코드 복잡성이 증가한다. 문제는 document의 구조와 사용법에서 온다.
첫 번째 원인은 DOM api는 HTML의 정적 트리를 변경 가능한(mutable) 그래프로 구성하기 때문이다.
DOM 트리는 트리 구조를 가지지만, documentapi는 양방향 그래프이며 부모와 자식이 서로 참조할 수 있다.
예를 들어, 자식 노드에서 일어나는 변경이 부모 노드에도 영향을 줄 수 있다.
DOM 트리가 변경되면, 그동안의 변경사항이 기록되지 않아 제거된 노드의 정보가 손실될 수 있다.
제거된 노드를 복원하는 것이 어렵고, 삭제된 HTML이 포함한 정보를 보존하기 어렵다.
이러한 문제를 참조 무결성이라고 한다.
참조 무결성을 지키려면 코드 복잡성이 증가한다.
코드 복잡성 증가의 두번째 원인은 함수 실행 시점의 관리이다.
특히 함수가 실행이 완료된 이후에 DOM 조작으로 발생하는 부수효과가 코드 복잡성을 높인다.
생명주기가 끝난 함수 내부 데이터가 필요해지는 경우가 많아 추가적인 로직이 필요해진다.
DOM 조작은 트리 계층에 영향을 미쳐, 부모 노드의 변경이 모든 자식 노드에 영향을 미칠 수 있다.
앞선 문제인 참조 무결성을 지키는 추가적인 로직이 필요하다.
이러한 문제들은 HTML을 DOM으로 변환하여 조작할 때 발생하기 쉬운 문제들이다.
코드가 증가할 수록 함수 실행 시점의 관리 복잡성이 증가한다.
근본적 원인은 DOM API와 HTML 구조에 일관성이 부족하기 때문이다. 이에 대한 해결은 1) HTML 계층을 불필요하게 변경하지 않으면서 2) DOM 조작 실행 타이밍을 일관되게 관리할 로직이 필요하다.
노드 격리화: 부모-자식 노드 간 영향이 제한되도록 설계.
함수 생명주기 관리: DOM 조작 이후에도 유지해야 할 데이터를 관리하도록 한다.
HTML 계층에 영향을 주지 않으면서, DOM 조작이 가져오는 부수효과를 관리하기 위한 함수와 저장 데이터의 로직을 구성한다.
이러한 문제를 염두한 DOM 조작 JS 스크립트를 작성해보자.
각 노드 계층을 분명하게 격리하고, 노드 단위로 DOM 조작을 관리한다.
DOM 조작의 영향을 부모나 형제 노드에 전달하지 않고, 자식 노드만 변경하도록 설계한다.
(리)렌더링이 각 노드에서 일관되게 작동하도록 추상화한다.
DOM api가 일으킬 문제를 방지하기 위해 이러한 전략을 사용할 수 있다. 하지만 한계도 분명하다. 무엇보다 문제 해결에 여전히 많은 코드가 필요하고, 결국엔 복잡성이 증가...