'모던 자바스크립트 Deep Dive' 책의 일부분을 정리한 글입니다.
대부분의 프로그래밍 언어는 운영체제나 가상머신 위에서 실행되지만 자바스크립트는 브라우저에서 HTML, CSS와 함께 실행된다. 따라서 개발자라면, 특히 프런트엔드 개발자라면 더더욱 브라우저 환경을 이해할 필요가 있다. 이 글에서는 브라우저가 HTML, CSS, 자바스크립트로 작성된 텍스트 문서를 어떻게 파싱하여 브라우저에 렌더링하는지 알아볼 것이다.
먼제 전체적인 과정은 아래와 같다.
- 브라우저는 HTML, CSS 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답을 받는다.
- 브라우저의 렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 파싱하여 DOM과 CSSOM을 생성하고 이들을 결합하여 렌더 트리를 생성한다.
- 브라우저의 자바스크립트 엔진은 서버로부터 응답된 자바스크립트 파일을 파싱하여 AST(Abstract Syntax Tree)를 생성하고 바이트코드로 변환하여 실행한다. 이때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다. 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합된다.
- 렌더 트리를 기반으로 HTML 요소의 레이아웃(위치와 크기)을 계산하고 브라우저 화면에 HTML 요소를 페인팅한다.
파싱 (parsing)
: 텍스트 문서를 토큰화하고, 토큰에 문법적 의미와 구조를 반영하여 parse tree를 생성하는 일련의 과정
렌더링 (rendering)
: 파싱 결과가 결합된 렌더 트리로 브라우저에 시각적으로 출력하는 것
1. 요청과 응답
브라우저의 역할은 필요한 리소스를 서버에 요청하고, 서버로부터 받은 응답값을 파싱하여 시각적으로 렌더링하는 것이다. 사용자가 직접 서버에 자원을 요청하는 방법 중 하나는 주소창에 URL을 입력하는 것이다. 이미 우리에게 너무나도 익숙한 방법이다.
브라우저 주소창에 URL을 입력하고 엔터 키를 누르면, URL 호스트 이름이 DNS를 통해 IP 주소로 변환되고 이 IP 주소를 갖는 서버에게 요청을 전송한다. 참고로 DNS를 거치는 과정을 DNS Lookup이라고 한다.
일반적으로 주소창에 URL을 입력해 자원을 받아오면, 이에 뒤따르는 연쇄적인 요청이 무수히 많이 발생한다. 이는 브라우저의 렌더링 엔진이 HTML을 파싱하는 도중에 link, img, script와 같이 외부 리소스를 로드하는 태그를 만나면 파싱을 일시 중단하고 해당 리소스 파일을 서버로 요청하기 때문이다.
2. HTTP 1.1 vs HTTP 2
HTTP/1.1 → 커넥션당 하나의 요청과 응답만 처리한다. 따라서 요청할 리소스의 개수에 비례하여 응답 시간도 증가한다.
HTTP/2 → 커넥션당 여러 개의 요청과 응답 처리 가능. 1.1에 비해 페이지 로드 속도가 약 50%정도 빠르다고 알려져 있다.
이 부분은 나중에 HTTP 파트에서 더욱 자세하게 다루겠다. 우선을 이렇게만 알고 넘어가자!
3. HTML 파싱과 DOM 생성
브라우저가 서버로부터 받은 HTML 파일은 단순히 텍스트 파일이다. 따라서 이를 브라우저가 이해할 수 있는 자료구조로 변환하여 메모리에 저장해야 하는데, 이를 '파싱'이라고 한다. HTML 파싱의 결과물은 DOM 이다. HTML의 파싱 단계는 아래와 같다.
- 서버에 존재하던 HTML 파일이 브라우저의 요청에 의해 응답된다. 서버는 HTML 파일을 바이트(2진수) 형태로 전송한다.
- 브라우저는 응답된 바이트 형태의 HTML 문서를 <meta>의 charset 속성에 지정된 인코딩 방식(보통 UTF-8)을 기준으로 문자열로 변환한다.
- HTML 텍스트 문서를 토큰화한다.
- 각 토큰들을 객체로 변환하여 노드를 생성한다.
- HTML 요소는 중첩 관계를 갖는데, 이를 반영하여 모든 노드들을 트리 자료구조로 구성한다. 이를 DOM이라 부른다.
4. CSS 파싱과 CSSOM 생성
렌더링 엔진은 DOM을 생성해 나가다가 CSS를 로드하는 태그(link, style)를 만나면 파싱을 일시 중단한다. 그리고 href에 지정된 CSS 파일을 서버에 요청해서 HTML과 동일한 파싱 과정(바이트 → 문자 → 토큰 → 노드 → CSSOM)을 거쳐 CSSOM을 생성한다.
CSS 파싱이 완료되면 렌더링 엔진은 다시 DOM 생성을 재개한다.
5. 렌더 트리 생성
렌더링 엔진은 DOM과 CSSOM를 생성하고난 뒤에 이 둘을 결합해 렌더 트리를 생성한다. 렌더 트리는 오직 렌더링을 위한 트리이기 때문에 브라우저 화면에 렌더링되는 노드만으로 구성된다.
이후 완성된 렌더 트리는 각 HTML 요소의 레이아웃(위치와 크기)을 계산하는 데 사용되며 브라우저 화면에 픽셀을 렌더링하는 페인팅 처리에 입력된다. 렌더링 과정은 아래의 경우 다시 실행된다.
- 자바스크립트에 의한 노드 추가 또는 삭제
- 뷰포트 변경 (창 resize)
- HTML 요소의 레이아웃(위치, 크기)에 변경을 발생시키는 스타일 변경. (width, margin, display 등)
리플로우는 레이아웃 계산을 다시 하는 것을 말하며, 노드 추가/삭제, 요소의 크기/위치 변경, 윈도우 리사이징 등 레이아웃에 영향을 주는 변경이 발생한 경우에 한하여 실행된다.
리페인트는 재결합된 렌더 트리를 기반으로 다시 페인트를 하는 것을 말한다.
따라서 리플로우와 리페인트가 반드시 순차적으로 실행되는 것은 아니다. 레이아웃에 영향이 없는 변경은 리플로우 없이 리페인트만 실행된다. 또한 리렌더링은 비용이 많이 드는 작업이기 때문에 최대한 줄이는게 성능에 도움 된다.
6. 자바스크립트 파싱과 실행
DOM 생성 과정에서 script 태그를 만나면 DOM 생성을 일시 중단하고, 메인 쓰레드는 렌더링 엔진이 아닌 자바스크립트 엔진이 점유하게 된다. 자바스크립트 엔진은 로드된 자바스크립트 파일을 파싱하고, 이를 저수준 언어로 변환하고 실행하는 역할을 한다. 자바스크립트의 파싱 결과물은 AST(Abstract Syntax Tree)이며, 이는 인터프리터가 실행할 수 있는 바이트코드로 변환되고 인터프리터에 의해 실행된다. V8 엔진의 경우 성능 최적화 작업까지 이루어진다고 한다.
7. 자바스크립트 파싱에 의한 HTML 파싱 중단
지금까지 알아본 바에 의하면 브라우저는 동기적으로 파싱하고 실행한다. 따라서 html 파일 내에서 script 태그의 위치는 굉장히 중요하다.
script 태그가 body 바로 위에 위치한 경우 DOM이 생성되기 전에 자바스크립트 파일이 실행된다. 만약에 자바스크립트에서 DOM API로 아직 생성되지 않은 요소에 접근한다면 어떻게 될까? 아마 개발자 도구의 콘솔창에서 에러 메세지를 발견할 수 있을 것이다. 이런 문제를 피하기 위해 script 태그를 body 요소 아래에 위치시키는 방법은 좋은 생각이다. 이유는 아래와 같다.
- DOM이 완성되지 않은 상태에서 자바스크립트가 DOM을 조작하면 에러가 발생할 수 있다.
- 자바스크립트 로딩/파싱/실행으로 인해 HTML 요소들의 렌더링에 지장받는 일이 발생하지 않아 페이지 로딩 시간이 단축된다.
8. async / defer 어트리뷰트
자바스크립트 파싱에 의해 DOM 생성이 중단되는 문제를 해결하기 위해 HTML5부터 script 태그에 async와 defer 어트리뷰트가 추가되었다. 이 두 가지 어트리뷰트는 src 어트리뷰트가 존재해야만 사용할 수 있다.
<script async src="index.js"></script>
<script defer src="index.js"></script>
<async 어트리뷰트>
HTML 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행된다. 자바스크립트의 파싱과 실행은 자바스크립트 파일의 로드가 완료된 직후 진행되며, 이때 HTML 파싱은 중단된다.
async는 script 태그의 순서와 상관없이 로드가 완료된 자바스크립트부터 먼저 실행되므로 순서가 보장되지 않는다는 특징이 있다. 따라서 순서 보장이 필요한 script 태그에는 async를 사용하면 안된다.
<defer 어트리뷰트>
HTML 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행된다. 자바스크립트 파싱과 실행은 DOM 생성이 완료된 직후에 진행된다.
<참조>
모던 자바스크립트 Deep Dive
'WEB' 카테고리의 다른 글
[ WEB ] 인터넷과 웹의 차이점 (0) | 2022.08.03 |
---|---|
[ WEB ] XML이란 (0) | 2022.08.02 |