CSSOM
스타일 트리
CSS를 파싱해 만든 객체 모델. 어떤 요소에 어떤 스타일이 적용되는지(상속·우선순위 포함) 계산한다.
CSSOM(CSS Object Model)은 브라우저가 모든 CSS 소스(외부 stylesheet, <style> 블록, 인라인 style, User-Agent 기본 스타일시트)를 bytes → characters → tokens → nodes 순서로 파싱해 만든 트리 형태의 객체 모델로, DOM과 별개로 존재하지만 최종적으로 결합되어 각 요소의 스타일을 계산하는 데 쓰인다. CSSOM은 부분적으로 적용할 수 없는데, 뒤에 나오는 규칙이 앞의 규칙을 덮어쓸 수 있어(cascade) CSSOM이 완성되기 전까지는 어떤 요소도 안전하게 렌더링할 수 없기 때문에 CSS는 기본적으로 렌더링 차단(render-blocking) 자원이다. 각 요소의 최종 스타일을 결정할 때 브라우저는 캐스케이드 원점/레이어(origin & importance) → 명시도(specificity) → 소스 순서(source order) 순으로 충돌을 해소하고, 상속(inheritance)으로 부모의 상속 가능 속성을 물려받은 뒤, 상대값(em, %, currentColor 등)을 절대값으로 환산하는 값 처리 단계(specified → computed → used → actual value)를 거친다. 명시도는 (인라인, ID, 클래스/속성/의사클래스, 타입/의사요소)의 4자리 가중치로 계산되며 !important와 CSS @layer(cascade layer)는 명시도를 넘어서는 우선순위 계층을 만든다. 최종 계산된 스타일은 getComputedStyle(el)로 조회할 수 있으며, 이것이 렌더 트리에 부착되는 실제 스타일이다.
내부 구성
핵심 포인트
- CSSOM은 bytes→characters→tokens→nodes→CSSOM 트리로 파싱되며 DOM과 병렬로 구성된다
- CSS는 render-blocking: CSSOM이 완성되기 전에는 첫 페인트가 지연된다 (미디어 쿼리로 비차단화 가능)
- 캐스케이드 우선순위: 원점/importance/@layer > 명시도(specificity) > 소스 순서
- 명시도는 (inline, ID, class·attr·pseudo-class, type·pseudo-element) 4자리로 계산된다
- 상속(inheritance): color, font 등은 부모에서 물려받고, inherit/initial/unset/revert 키워드로 제어
- 값 처리 파이프라인: specified value → computed value → used value → actual value
- getComputedStyle()은 캐스케이드·상속·기본값이 모두 반영된 최종 값을 반환한다
심화
면접에서 자주 나오는 함정: CSS는 render-blocking이지만 parser-blocking은 아니다. HTML 파서는 CSS를 기다리며 DOM을 계속 만들 수 있으나, <script>(특히 인라인/동기 스크립트)를 만나면 그 스크립트가 CSSOM을 읽을 수 있으므로 브라우저는 앞선 CSS의 CSSOM 구축이 끝날 때까지 스크립트 실행을 미루고, 그동안 DOM 파싱도 멈춘다. 즉 'CSS가 JS를 막고, JS가 DOM을 막는' 연쇄가 생긴다. 최적화 포인트는 media='print'나 미디어 쿼리로 조건부 CSS를 비차단으로 만들고, critical CSS를 인라인하고, 나머지를 지연 로드하는 것이다. 또 하나 놓치기 쉬운 점: 명시도 계산에서 :is()/:not()/:has()의 인자 중 가장 높은 명시도를 취하지만 :where()는 항상 명시도 0이라는 것, 그리고 인라인 스타일과 !important, @layer, 트랜지션/애니메이션이 얽히면 캐스케이드 순서가 '@layer 밖의 일반 선언 → @layer 안의 선언'처럼 직관과 반대로 동작한다는 점이다. 이런 세부는 실제 대규모 CSS 아키텍처(디자인 시스템)에서 스타일이 왜 안 먹히는지를 디버깅할 때 결정적이다.
쉽게 말하면 각 요소에 '어떤 옷(스타일)을 입힐지' 규칙표를 정리해 두는 것.