[TIL] 브라우저가 HTML, CSS, JS를 처리하는 과정: DOM, CSSOM, AST까지

승민·2025년 4월 10일
0

TIL

목록 보기
14/20

브라우저 렌더링 전체 흐름에 대한 설명은 브라우저의 렌더링을 참고해주세요.

웹 브라우저가 HTML, CSS, JavaScript를 받아서 화면에 렌더링하고 실행하는 과정은 복잡한 단계로 이루어집니다. 이 포스트에서는 DOM(Document Object Model), CSSOM(CSS Object Model), AST(Abstract Syntax Tree) 생성 과정을 하나씩 뜯어보고, 내부 동작을 자세히 알아보겠습니다.

HTML 파싱과 DOM 생성

HTML 파일이 브라우저에 도착하면 렌더링 엔진이 이를 파싱해 DOM 트리를 생성합니다.

DOM 생성 과정

| 1. 바이트 스트림 -> 문자 스트림

  • 브라우저는 서버에서 받은 HTML을 바이트 스트림으로 수신합니다.
  • <meta charset="UTF-8"> 같은 인코딩 정보를 읽고, 바이트를 문자 스트림(Character Stream)으로 변환합니다(예: UTF-8 → 유니코드 문자).

| 2. 토큰화(Tokenization)

  • 문자 스트림은 토크나이저(Tokenizer)에 의해 토큰으로 분해됩니다.
  • 토큰은 HTML의 기본 단위(태그, 속성, 텍스트 등)입니다.
<div class="box">Hello</div>
<div  시작 태그 시작
class="box"  속성
> → 시작 태그 끝
Hello → 텍스트 노드
</div> → 종료 태그

| 3. 파싱과 DOM 트리 구축

  • 토큰은 파서(Parser)에 전달되어 DOM 트리로 변환됩니다.
  • 파서는 스택 기반 알고리즘을 사용:
    시작 태그(<div>)를 만나면 스택에 쌓고, 종료 태그(</div>)를 만나면 스택에서 꺼내 부모-자식 관계를 완성.
<html>
  <body>
    <div>Hello</div>
  </body>
</html>
Document
└── html
    ├── head
    └── body
        └── div
            └── Text: "Hello"

| 4. 비동기 및 블록킹 요소

  • <script src="...">를 만나면 HTML 파싱이 중단(블록킹)되고, JS 파일을 다운로드 후 실행.
    <script async>: 다운로드와 파싱을 병렬로 진행, 다운 완료 시 실행.
    <script defer>: DOM 완성 후 실행.
    <link rel="stylesheet">: CSS 파일을 처리하며 렌더링이 지연됨.

CSS 파싱과 CSSOM 생성

HTML에서 <link><style> 태그를 만나면 CSS를 파싱해 CSSOM을 만듭니다.

과정

| 1. 바이트 스트림 -> 문자 스트림

  • CSS 파일을 바이트 스트림에서 문자 스트림으로 변환(인코딩 해석)

| 2. 토큰화

  • 토크나이저가 CSS 코드를 토큰으로 나눕니다
    div { color: red; font-size: 16px; }
    div → 선택자
    { → 규칙 시작
    color: red → 선언
    ; → 선언 구분
    font-size: 16px → 선언
    } → 규칙 끝

| 3. 파싱과 CSSOM 트리 구축

  • 토큰은 파서에 의해 CSS 규칙 객체(Rule Object)로 변환.
  • CSSOM은 DOM과 유사한 트리 구조로, 스타일 규칙과 요소를 연결
  • 계층적 상속: 부모 요소의 스타일이 자식에게 적용.
  • 캐스케이드(Cascade): 우선순위 계산(예: !important > 특정성 > 선언 순서).
body {
  margin: 0;
}
div {
  color: red;
}
CSSStyleSheet
├── Rule: "body { margin: 0; }"
└── Rule: "div { color: red; }"

| 4. 렌더링 블록킹

  • CSSOM이 완성되기 전까지 렌더링이 멈춤.
  • 브라우저는 빈 화면(FOUC: Flash of Unstyled Content)을 피하기 위해 CSS 처리를 기다립니다.

JS 파싱과 AST 생성

<script> 태그를 만나면 JavaScript 엔진(예: V8)이 코드를 처리합니다.

| 1. 바이트 스트림 -> 문자 스트림

  • JS 파일을 바이트 스트림에서 문자 스트림으로 변환.

| 2. 토큰화

  • 렉서(Lexer)가 코드를 토큰으로 분해.
  • 렉서는 소스 코드를 읽고 키워드, 식별자, 연산자 등으로 나누는 어휘 분석기(Lexical Analyzer)입니다.
function add(a, b) {
  return a + b;
}
/*
   function → 키워드
  add → 식별자
  ( → 구분자
  a → 식별자
  , → 구분자
  b → 식별자
  ) → 구분자
  { → 블록 시작
  return → 키워드
  a → 식별자
  + → 연산자
  b → 식별자
  ; → 구분자
  } → 블록 끝
  */

| 3. 구문 분석과 AST 생성

  • 토큰은 파서에 의해 AST(Abstract Syntax Tree)로 변환.
  • AST는 코드의 문법적 구조를 트리로 표현
    재귀 하향 파싱(Recursive Descent Parsing): 문법을 검사하며 트리 생성.
var x = 10;

AST (간소화)

VariableDeclaration
└── VariableDeclarator
    ├── Identifier: "x"
    └── Literal: 10

| 4. 프리컴파일과 호이스팅

  • AST 생성 중 프리컴파일 단계에서 변수와 함수 선언이 스코프에 등록.
    var x: 선언만 호이스팅, undefined로 초기화.
    function add() {}: 선언과 정의 모두 호이스팅.
    let, const: 호이스팅되지만 TDZ(Temporal Dead Zone)로 관리.

| 5. 코드 생성과 실행

  • AST는 바이트코드(Bytecode)로 변환(V8의 Ignition).
  • 자주 실행되는 코드는 JIT 컴파일러(V8의 TurboFan)가 기계어로 최적화.
  • 실행 중 DOM/CSSOM과 상호작용(예: document.body.style.color = "blue").

정리

  • HTML → DOM: 바이트 → 토큰 → 트리.
  • CSS → CSSOM: 스타일 규칙으로 렌더링 준비.
  • JS → AST: 코드 실행 준비 및 최적화.

참고

0개의 댓글