- 전체적인 브라우저 렌더링 과정
- HTML 파싱과 DOM 생성
- CSS 파싱과 CSSOM 생성
- 렌더 트리 생성
- 자바스크립트 파싱과 실행
- 리플로우와 리페인트
- 자바스크립트 파싱에 의한 HTML 파싱 중단
- script 태그의 async/defer 어트리 뷰트
모던 자바스크립트 38장
브라우저 렌더링 과정
웹 동작 방식
<1> 브라우저는 HTML, CSS, 자바스크립트, 이미지, 폰트파일 등 렌더링에 필요한 리소스를 서버에 요청하고 응답받습니다. => 웹 동작방식
<2> 브라우저의 렌더링 엔진은 서버로부터 응답받은 HTML과 CSS를 파싱
하여 DOM과 CSSOM을 생성하고, 이 둘을 결합하여 렌더트리를 생성한다.
<3> 브라우저의 자바스크립트 엔진은 서버로부터 응답받은 자바스크립트를 파싱
하여 AST(Abstact Syntax Tree)
를 생성하고 바이트 코드
로 변환하여 실행합니다.
자바스크립트가 DOM API
를 통해 DOM이나 CSSOM을 변경한다면, 변경된 DOM과 CSSOM
을 다시 렌더트리로 결합
됩니다.
<4> 결합된 렌더트리를 기반으로 HTML요소의 레이아웃을 계산하고 브라우저에 HTML 요소를 페인팅합니다.
파싱
: 프로그래밍 언어의 문법에 맞게 작성된 텍스트 문서를 읽어 들여 실행하기 위해 문자열을 의미있는 토큰(token)으로 분해하고, 문법적 의미와 구조를 반영하여 트리형태의 자료구조인 파스(parse)트리를 만드는 과정을 말한다.
브라우저 렌더링 과정 <2>번에 해당하는 내용이다.
<1>번에 의해 서버로부터 응답받은 html 파일은 이진수의 파일(바이트 코드)이 되어있다.
브라우저는 1번 과정에서 받은 바이트 코드를 받게 됩니다. 그리고 응답된 바이트 형태의 HTML 문서는 meta태그의 charset 어트리뷰트에 의해 지정된 인코딩 방식(ex. utf-8)을 기준으로 문자열을 변환
한다.
<1>번 과정에서 서버에서 브라우저에게 응답할 때 응답 헤더에 담겨서 응답됩니다.
문자열로 변환된 HTML 문서를 읽어 들여 문법적 의미를 갖는 최소 단위인 토큰
들로 분해한다. (파싱
)
각 토큰들을 객체로 변환하여 노드
들을 생성한다. 이 노드는 DOM을 구성하는 기본 단위가 됩니다.
HTML 문서는 HTML요소들의 집합으로 이루어지면 HTML 요소는 중첩 관계를 갖는다. => 이러한 중첩관계는 트리 자료구조를 생성하게 되는데 이를 DOM트리라 한다.
즉,
DOM
은HTML 문서를 파싱한 결과물
이다.
브라우저 렌더링 과정 <2>번에 해당하는 내용이다.
렌더링 엔진은 HTML을 처음부터 한 줄씩 순차적으로 파싱하여 DOM을 생성한다. 렌더링 엔진은 DOM을 생성해 나가다가 CSS를 로드하는 link 태그나 style 태그를 만나면 DOM 생성을 일시 중지한다.
그리고 link 태그의 href 어트리뷰트에 지정된 CSS파일을 서버에 요청하여 로드한 CSS파일이나 style태그 내의 CSS를 HTML과 동일한 파싱 과정(바이트 -> 문자 -> 토큰 -> 노드 -> CSSOM)을 거치며 해석하여 CSSOM
을 생성한다.
이후 CSS 파싱을 완료하면 HTML 파싱이 중단된 지점부터 다시 HTML을 파싱하기 시작하여 DOM 생성을 재개한다.
이런 이유로 CSS의 우선순위가 뒤에 있을 수록 적용되는 지 알 수 있다.
브라우저 렌더링 과정 <2>번에 해당하는 내용이다.
렌더링 엔진은 앞서 만든 DOM과 CSSOM을 합쳐 자바스크립트 엔진이 읽을 렌더 트리를 구축한다.
완성된 렌더트리
는 각 HTML요소의 레이아웃
을 계산하는데 사용되며 브라우저 화면에 픽셀을 렌더링하는 페인팅
처리에 입력된다.
렌더 트리는 브라우저 화면에 렌더링 되는 노드만을 구성한다.
즉, 브라우저 화면에 렌더링되지 않는 노드(ex.meta
,script
,css {dispaly:none}
) 되는 요소는 포함하지 않는다.
리렌더링이 많이 발생하게 되면 브라우저 성능이 안 좋아진다.
브라우저 렌더링 과정 <3>번에 해당하는 내용이다.
DOM은 HTML 문서의 구조와 정보뿐만 아니라 HTML 요소와 스타일 등을 변경할 수 있는 프로그래밍 인터페이스로서 DOM API를 제공한다. ( 위와 같은 이유로 리렌더링이 발생 )
자바스크립트의 파싱은 CSS파싱과 마찬가지로 렌더링 엔진이 HTML 문서를 한 줄씩 읽어 나가다 script태그
를 만나면 자바스크립트 엔진
에 제어권을 넘기며 시작됩니다.
이후 자바스크립트 파싱과 실행이 종료되면 렌더링 엔진으로 다시 제어권을 넘겨 HTML 파싱이 중단된 시점
부터 다시 HTML 파싱을 시작하여 DOM 생성을 재개
한다.
자바스크립트 파싱과 실행은 브라우저의 렌더링 엔진이 아닌 자바스크립트 엔진
이 처리한다.
자바스크립트 엔진 또한 렌더링 엔진과 마찬가지로 자바스크립트 코드를 해석하여 AST(추상적 구문 트리)를 생성한다. 그리고 AST를 기반으로 인터프리터가 실행할 수 있는 중간 코드인 바이트 코드를 생성하여 실행한다.
- 토크나이징 : 단순한 문자열인 자바스크립트 소스코드를 어휘 분석하여 문법적 의미를 갖는 코드의 최소 단위인 토큰으로 분해한다. 이과정을
렉싱
이라고 부르기도 하지만 토크나이징과 미묘한 차이가 있다.
- 파싱 : 토큰들의 집합을 구문분석하여 AST(추상적 구문 트리)를 생성한다. AST는 토큰에 문법적 의미와 구조를 반영한 트리 구조의 자료구조이다.
(AST는 컴파일러나 인터프리터 이외에도 트랜스파일러(Prettier, Babel, Typescript) 를 구현할 수도 있다.
자바스크립트 코드에 DOM이나 CSSOM을 변경하는 DOM API가 사용된 경우 DOM이나 CSSOM이 변경된다. 이때 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합되고 변경된 렌더 트리를 기반으로 레이아웃과 페인트 과정을 거쳐 브라우저의 화면에 다시 렌더링한다.
레이아웃을 다시 계산하는 것
ex) 노드 추가/삭제, 요소 크기/위치 변경, 윈도우 리사이징 등
재결합된 렌더 트리를 기반으로 다시 페인트 하는 것
앞서 html, css, js는 모두 위에서 아래 방향으로 순차적으로 파싱하고 실행한다.
따라서 script의 태그의 위치에 따라 html 파싱이 블로킹되어 DOM생성을 지연될 수 있다는 것을 의미한다.
예제 코드)
<head>
<link rel = "stylesheet" href = "style.css">
<script>
$apple = document.querySelector("#apple");
</script>
</head>
<body>
<div id = "apple">apple</div>
</body>
위와 같이 코드를 구성할 경우 apple이라는 id가 선언되지 않았음에도 자바스크립트 파일에서는 #apple이라는 dom selector를 찾는다. 이와 같은 경우 에러가 발생할 것이다.
따라서 script파일을 가장 아래에 위치시키는 것이 가장 좋은 아이디어이다.
앞서 자바스크립트 파싱에 의한 DOM 생성이 중단 되는 문제를 근본적으로 해결하기 위해 HTML5부터 script 태그에 async와 defer 어트리뷰트가 추가되었다.
async와 defer 어트리뷰트를 사용하면 javascript 코드 파싱이 비동기적으로 이루어진다. 하지만 자바스크립트의 실행 시점에는 차이가 있다.
script src 에서만 사용이 가능하다. => 인라인 코드 작성은 적용이 안된다.