Browser DOM and Script Loading
"브라우저는 HTML을 어떻게 DOM으로 만들고, JavaScript는 언제 어떤 방식으로 DOM에 접근해야 안전한가?" 이 page는 그 질문에 답한다. DOM은 정적인 HTML 텍스트를 브라우저가 이해하도록 메모리에 올린 객체 기반 트리 구조이고, JavaScript는 이 DOM 인터페이스로 화면 내용을 바꾼다.[1] Vue.js가 줄이려는 "직접 DOM 조작"의 대상이 이 트리이고, SPA and CSR에서 browser가 JavaScript로 UI를 rendering할 때 바꾸는 대상도 이 트리라고 볼 수 있다. 언어 규칙 자체는 JavaScript Language Model이 맡고, 이 page는 그 언어가 브라우저 문서와 만나는 지점을 다룬다.
DOM은 언어 밖의 API다
- DOM API,
window,document,fetch는 JavaScript 실행 환경인 브라우저가 제공하는 API이며, ECMAScript 핵심 문법과는 구분된다.[2] - 그래서 "JavaScript로 화면을 바꾼다"는 말은 언어 문법이 아니라 브라우저가 노출한 DOM 인터페이스를 호출한다는 뜻으로 읽는 편이 정확하다고 볼 수 있다. 이 구분은 두 원문 문장을 이은 이 Wiki의 해석이다.
HTML에서 DOM 트리로
- 브라우저 엔진은 HTML을 아래 네 단계로 DOM으로 만든다.[3]
| 단계 | 과정명 | 설명 |
|---|---|---|
| Step 1 | Conversion | 서버에서 받은 바이트(Bytes) 데이터를 지정된 인코딩에 따라 문자열로 변환 |
| Step 2 | Tokenization | 문자열을 <html>, <body> 같은 의미 있는 단위인 토큰(Token)으로 분해 |
| Step 3 | Lexing | 각 토큰을 속성과 규칙을 가진 노드(Node) 객체로 변환 |
| Step 4 | Construction | 노드 간의 부모-자식 관계를 파악하여 최종적인 트리 구조(DOM) 완성 |
- 원문 제목은 이 과정을 "Critical Rendering Path"라고 부르지만, 표가 다루는 범위는 HTML을 읽어 DOM을 만드는 과정까지다.[4] 그래서 이 page는 이 표를 "DOM 생성 단계"로만 다룬다. DOM 이후의 rendering 단계는 원문에 없어 다루지 않는다.[5]
- AST와 DOM은 둘 다 텍스트를 파싱해 만든 트리 데이터 구조다. AST는 코드를 실행하기 위한 분석용 중간 단계이며 실행 후에는 보통 사라지고, DOM은 브라우저 화면을 유지하고 조작하기 위한 상주용 최종 결과물이며 JavaScript로 언제든 수정할 수 있다.[6]
Script가 DOM 생성을 멈추는 방식
- 브라우저는 HTML을 읽다가
<script>를 만나면 일단 멈춘다. JavaScript 파일을 내려받아 실행할 때까지 DOM 생성이 중단된다.[7] - 아래 그림은 원문의 이 설명을 흐름으로 옮긴 것이다. script 실행이 끝나면 파싱이 이어진다는 표시는 "실행할 때까지 멈춘다"는 원문 표현을 그림으로 옮긴 이 Wiki의 해석이다.
flowchart TD
Parse["HTML을 읽으며 DOM 노드 생성"] --> Meet{"script를 만났는가"}
Meet -- "아니오" --> Parse
Meet -- "예" --> Block["DOM 생성 중단: JavaScript 다운로드와 실행"]
Block --> Parse<head>의 script가 DOM을 조작하려 하면, 아래쪽 HTML 요소가 아직 노드로 생성되지 않아null에러가 난다.[8]- 스크립트 실행이 길어지면 화면이 뜨지 않아 사용자가 답답해할 수 있다.[9]
- 그래서 DOM 로딩이 끝난 뒤 쓰도록 script를 일반적으로
<body>아래에 둔다. HTML 파일 안<script>영역의 코드는 그 페이지 안에서만 재사용할 수 있다.[10] 아래 예제는 같은 문서에서 head 안과 body 뒤에 script를 둔 형태다.[10:1]
<!DOCTYPE html>
<html>
<head>
<title>JavaScript</title>
<script>
console.log("Hello JavaScript!! - title");
</script>
</head>
<body>
<h1>Hello</h1>
</body>
<script>
console.log("Hello JavaScript!! - body");
</script>
</html>
Script 배치와 실행 시점
- 브라우저에서 ESM을 쓰려면 script에
type="module"을 지정한다.[11] - Module script는 파일마다 독립 모듈 스코프를 갖고, 기본적으로 strict mode로 실행되며,
import·export와 top-level await를 쓸 수 있다. 실행은 기본적으로defer처럼 HTML 파싱 후에 된다. 반면 일반 script는 전역 스코프를 공유한다.[11:1] top-level await는 JavaScript Asynchronous Execution에서 다룬다. - 아래 표는 세 가지 배치를 같은 질문으로 비교한 것이다. "DOM 접근 위험" 열의 body 끝과 module 칸은 원문 규칙에서 끌어낸 이 Wiki의 해석이다.
| 배치 | 실행 시점 | 스코프 | DOM 접근 위험 |
|---|---|---|---|
<head>의 일반 script |
HTML을 읽다가 만나는 즉시 내려받아 실행하고, 그동안 DOM 생성이 멈춘다.[7:1] | 전역 스코프를 공유한다.[11:2] | 아래쪽 요소가 아직 노드가 아니어서 null 에러가 난다.[8:1] |
<body> 아래(뒤)의 일반 script |
DOM 로딩이 끝난 뒤 쓰려고 <body> 아래에 둔다.[10:2] |
전역 스코프를 공유한다.[11:3] | 앞쪽 요소는 이미 노드로 만들어져 있어 위험이 줄어든다고 볼 수 있다. |
type="module" script |
기본적으로 defer처럼 HTML 파싱 후 실행된다.[11:4] |
파일마다 독립 모듈 스코프를 갖고 strict mode로 실행된다.[11:5] | 파싱 후 실행되므로, head 배치의 미생성 노드 문제를 피하는 또 다른 방법으로 읽을 수 있다. |
<script>의defer·async속성 자체는 원문에 없다.[12]- Vue app instance는
created시점에는 아직 DOM에 mount되지 않아 DOM 접근이 실패하고, DOM 접근은mounted에서 해야 한다고 설명한다. 이 실패는 head script가 아직 만들어지지 않은 노드에 접근해null에러를 내는 문제와 같은 구조로 읽을 수 있다. 두 경우 모두 "접근하려는 DOM 노드가 아직 존재하는가"가 기준이라는 연결은 이 Wiki의 해석이다.
이벤트 handler 연결
- Inline 방식은 callback을 등록하지 않고 HTML 태그 속성으로 handler를 직접 실행하는 형태다. HTML과 JavaScript가 섞여 관심사가 분리되지 않으므로 유지보수가 어렵고 재사용성이 떨어진다.[13]
- 이벤트 객체는 각 이벤트의 상세 정보를 담는다.[14]
- Vue Directives는
v-on이 element에 native DOM event listener를 연결한다고 설명한다. Inline handler의 관심사 분리 문제와, template에서 state와 handler를 선언적으로 연결하는 Vue 방식을 대비해 읽을 수 있다. 이 대비는 이 Wiki의 해석이다. addEventListener방식의 등록과 이벤트 전파(bubbling, capturing, 전파 제어)는 원문에 내용이 없거나 모호해 다루지 않는다.[15][16]
관련
- JavaScript Language Model: ECMAScript 문법과 호스트 DOM API의 경계, ESM 모듈 시스템을 언어 쪽에서 설명한다.
- JavaScript Asynchronous Execution: module script에서 쓸 수 있는 top-level await와, 사용자 이벤트 같은 비동기 작업의 흐름을 설명한다.
- Vue.js: Vue는 직접 DOM 조작을 state 선언으로 바꾸는 모델이고, 이 page는 그 조작 대상인 DOM 트리를 설명한다고 볼 수 있다.
- Vue app instance:
created와mounted의 DOM 접근 시점이 head script의 미생성 노드 문제와 같은 구조로 읽힌다. - Vue Directives:
v-on이 native DOM event listener를 연결하므로, inline handler 방식과 대비해 읽을 수 있다. - SPA and CSR: CSR은 browser가 JavaScript로 UI를 rendering하는 모델이고, 이 page의 DOM이 그 rendering이 바꾸는 대상이라고 볼 수 있다.
테스트 질문
<head>의 일반 script에서<body>안 요소를 찾으면 왜 실패하는가?<body>아래(뒤) 배치와type="module"은 이 문제를 각각 어떻게 피하는가?- AST와 DOM은 둘 다 파싱 결과인데 수명과 용도가 어떻게 다른가?
출처
- 출처 메모: 주 source인 DOM.md (HTML)은 질문에 답하는 대화체로 쓰였다(29줄 "질문하신 … 비교해 봅시다", 57줄 "훨씬 기억에 오래 남을 거예요!"). LLM 대화 답변을 옮긴 개인 노트로 보이며, 외부 참고 표기가 없다. DOM 생성 4단계 표와 AST 비교는 이 노트의 표현이며, 비슷한 단계 설명이
Raw/courses/Naver BoostCamp/Challenge/Day3/학습정리.md140줄부터 있지만 이 page는 그 노트나 MDN·HTML 표준과 교차 확인하지 않았다.
DOM.md (HTML) 12줄 — "DOM은 정적인 HTML 텍스트를 브라우저가 이해할 수 있도록 메모리에 적재한 객체 기반의 트리 구조입니다. 자바스크립트는 이 DOM이라는 인터페이스(API)를 통해 화면의 내용을 동적으로 변경합니다." ↩︎
ECMAScript.md (Study) 42줄 — "브라우저의 DOM API,
window,document,fetch같은 기능은 JavaScript 실행 환경이 제공하는 API이며, ECMAScript 자체의 핵심 문법과는 구분된다." ↩︎DOM.md (HTML) 16-23줄 — "브라우저 엔진이 HTML을 읽어 DOM을 만드는 과정은 다음과 같습니다." 네 단계 표를 굵은 글씨 표기만 빼고 그대로 옮겼다. ↩︎
DOM.md (HTML) 14, 16줄 — 절 제목 "DOM 생성 4단계 (Critical Rendering Path)"와 "브라우저 엔진이 HTML을 읽어 DOM을 만드는 과정은 다음과 같습니다." ↩︎
출처 공백 — CSSOM, render tree, layout, paint처럼 DOM 생성 이후의 rendering 단계는 이 batch의 어느 Raw에도 없다. ↩︎
DOM.md (HTML) 31-37줄 — 공통점 "텍스트를 파싱하여 만든", AST "코드를 실행하기 위한", "실행 후에는 보통 사라짐", DOM "브라우저 화면을 유지하고 조작하기 위한", "자바스크립트로 언제든 수정 가능". ↩︎
DOM.md (HTML) 44-46줄 — "브라우저는 HTML을 읽다가
<script>를 만나면", "자바스크립트 파일을 다운로드하고 실행할 때까지 DOM 생성이 멈춥니다." ↩︎ ↩︎DOM.md (HTML) 48줄 — "만약
<head>에서 DOM을 조작하려 하면, 아직 밑에 있는 HTML 요소들이 노드로 생성되지 않아null에러가 발생합니다." ↩︎ ↩︎DOM.md (HTML) 50줄 — "스크립트 실행이 길어지면 화면이 뜨지 않아 사용자가 답답해할 수 있습니다." ↩︎
JavaScript.md 27-49줄 — "일반적으로 DOM 로딩이 완료된 후 사용하기 위해 <body>태그 아래에 위치", "페이지 내에서만 재사용 가능". HTML 예제 30-49줄을 그대로 옮겼다. DOM.md (HTML) 42줄의 절 제목도 "
<script>태그가 하단에 위치해야 하는 이유"다. ↩︎ ↩︎ ↩︎ECMAScript.md (Study) 980-994줄 — "브라우저에서 ESM을 사용하려면 script 태그에
type="module"을 지정한다.", "각 파일이 독립적인 모듈 스코프를 가진다.", "기본적으로 strict mode로 실행된다.", "import,export를 사용할 수 있다.", "top-level await를 사용할 수 있다.", "기본적으로defer처럼 HTML 파싱 후 실행된다.", "일반 script는 전역 스코프를 공유하지만, module script는 파일마다 독립된 스코프를 가진다." ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎출처 공백 — 이 batch의 Raw에는 module script가 "
defer처럼" 실행된다는 비유(ECMAScript.md (Study) 992줄)만 있고,defer·async속성을 직접 설명한 줄은 없다. ↩︎DOM.md (JavaScript) 37-38줄 — "inline 스타일: callback을 등록하지 않고 html 태그의 속성으로 handler를 직접 실행하는 형태", "html과 javascript가 섞임 - 관심사 분리가 되지 않아 유지보수가 어렵고 재사용성이 떨어짐". 바로 아래 39줄의 inline handler 안
this에 관한 문장은 확인할 근거가 없어 옮기지 않았다. ↩︎Event.md 15-16줄 — 절 제목 "이벤트 객체의 활용"과 "각 이벤트 별로 이벤트에 대한 상세 정보 포함" ↩︎
출처 공백 — Event.md 23-24줄의 "이벤트는 적용 대상 자식 요소들에게도 동일하게 발생되며 handler가 있다면 처리됨"은 전파 방향을 정하지 않아 모호하고, 28줄 "이벤트 진행 제어"는 제목만 있다. bubbling, capturing,
stopPropagation, 이벤트 위임은 MDN DOM events 같은 공식 자료가 들어올 때 다룬다. ↩︎출처 공백 — DOM.md (JavaScript) 9줄과 Event.md 8줄의 "event와 event listener"는 제목만 있다. 같은 노트 36줄 "event listener 등록 방식" 아래에는 inline 방식만 있다. ↩︎