간단하게 보아서, 브라우저는 다음과 같은 과정으로 렌더링을 수행한다.
- 브라우저가 렌더링에 필요한 리소스를 서버로 요청, 응답을 받는다.
- 브라우저 렌더링 엔진이 응답받은 HTML과 CSS를 파싱해 DOM, CSSOM을 생성하고 이들을 결합해 렌더 트리를 생성한다.
- 브라우저 JS 엔진은 응답된 JS를 파싱해 AST(Abstract Syntax Tree)를 생성하고 바이트코드로 변환해 실행한다. 이때 JS는 DOM API로 DOM, CSSOM을 변경할 수 있다. 변경된 이들은 다시 렌더 트리로 결합된다.
- 최종 렌더 트리를 기반으로 HTML 요소의 레이아웃을 계산해 브라우저 화면에 HTML 요소를 페인팅한다.
1. 요청과 응답
브라우저는 서버에 렌더링에 필요한 요소들을 요청하고 응답받는다.
브라우저의 렌더링 엔진은 HTML을 파싱하는 도중 CSS 파일을 로드하는 link 태그, 이미지 파일을 로드하는 img 태그, JS를 로드하는 script 태그 등을 만나면 HTML 파싱을 중단하고 해당 리소스 파일을 서버로 요청한다.
2. HTML 파싱과 DOM 생성
브라우저 렌더링 엔진은 응답받은 HTML 문서를 파싱해 DOM(Document Object Model)을 생성한다.
3. CSS 파싱과 CSSOM 생성
렌더링 엔진이 HTML을 순차적으로 파싱하며 DOM을 생성해 나가다가 CSS를 로드하는 태그 link, style 태그를 만나면 DOM 생성을 중단하고 CSS 파일을 요청하여 응답받는다.
그리고 이것을 HTML과 동일한 파싱 과정을 거쳐 해석하여 CSSOM(CSS Object Model)을 생성한다.
CSSOM은 CSS의 상속을 반영해 생성된다.
body { font-size: 18px; }
ul { list-style-type: none; }
4. 렌더 트리 생성
렌더링 엔진은 DOM과 CSSOM을 결합해 렌더링을 위한 렌더 트리를 만든다.
렌더 트리는 렌더링을 위한 트리다. 브라우저 화면에 렌더링 되지 않는 노드와 CSS에 의해 가려진 노드는 포함하지 않는다.
렌더 트리는 HTML 요소의 레이아웃을 계산하는데 사용되며 브라우저 화면에 픽셀을 렌더링하는 페인팅 처리에 입력된다.
지금까지의 렌더링 과정은 반복되어 실행될 수 있다.
(JS에 의한 노드 추가-삭제, 브라우저 창 리사이징에 의한 뷰포트 크기 변경, HTML 요소의 레이아웃 변경을 발생시키는 스타일 변경 등..)
리렌더링이 빈번하게 발생하지 않게 주의할 필요가 있다.
5. JS 파싱과 실행
HTML 파싱 중 script태그를 만나면,
- 파싱과 DOM 생성을 일시 중지하고
- script태그의 src어트리뷰트에 정의된 JS 파일을 서버에 요청해 응답받고
- 이 JS 코드를 파싱하기 위해 JS 엔진에 제어권을 넘긴다
이후 JS 파싱, 실행이 종료되면 렌더링 엔진으로 다시 제어권을 넘겨 중단된 지점부터 다시 HTML 파싱을 시작해 DOM 생성을 재개한다.
JS 파싱, 실행은 브라우저 렌더링 엔진이 아닌 JS 엔진이 처리한다.
JS 엔진이 하는 일은 JS 코드를 파싱해 저수준 언어로 변환하여 CPU가 실행하게끔 하는 역할을 한다.
JS 엔진은 JS를 해석하여 AST(Abstract Syntax Tree)를 생성한다.
이를 기반으로 인터프리터가 실행할 수 있는 중간 코드인 바이트코드를 생성하여 실행한다.
6. 리플로우, 리페인트
JS 코드에 DOM API가 사용된 경우 DOM이나 CSSOM이 변경된다.
변경된 DOM, CSSOM은 다시 렌더 트리로 결합되고 이를 기반으로 레이아웃과 페인트 과정을 거쳐 브라우저 화면에 다시 렌더링한다.
이를 리플로우, 리페인트라 한다.
리플로우는 레이아웃 계산을 다시 하는 것이며, 리페인트는 다시 페인트를 하는 것을 말한다.
7. JS 파싱에 의한 HTML 파싱 중단
JS 파싱과 HTML 파싱이 동기적으로 이루어지기 때문에 script 태그 위치에 따라 HTML 파싱이 블로킹되어 DOM 생성이 지연될 수 있다.
만약 DOM 생성이 아직 안되었는데 script 태그에서 DOM API를 호출하면 문제가 발생할 수도 있다.
이러한 이유로 script 태그를 바디 요소 가장 아래에 위치시킨다.
8. async/defer 어트리뷰트
위 블로킹 문제의 근본 해결을 위해 script 태그에 추가되었다.
이들을 사용하면 HTML 파싱과 외부 JS 파일의 로드가 비동기적으로 진행된다. 하지만 JS 실행 시점에 차이가 존재한다.
async
JS 파싱과 실행은 JS 파일의 로드가 완료된 직후 진행되며 이때 HTML 파싱이 중단된다.
순서 보장이 필요한 script 태그에는 async 어트리뷰트를 지정하지 않아야 한다.
defer
JS 파싱과 실행은 HTML 파싱이 완료된 직후 진행된다. 순서 보장이 필요한 script 태그에 유용하다.
'모던자바스크립트-DeepDive' 카테고리의 다른 글
8. 타이머 (0) | 2024.10.04 |
---|---|
7. 이벤트 (0) | 2024.10.03 |
5. 배열 (2) | 2024.10.02 |
4. 클로저 (0) | 2024.10.02 |
3. 실행 컨텍스트 (0) | 2024.10.01 |