2024. 3. 25. 15:11ㆍ자바스크립트
렌더링 과정
- 브라우저는 HTML, CSS, 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 서버에 요청하고 서버로부터 응답을 받는다.
- 브라우저의 렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 파싱하여 DOM과 CSSOM을 생성하고 이들을 결합한 렌더 트리를 생성한다.
- 브라우저의 자바스크립트 엔진은 서버로부터 응답된 자바스크립트를 파싱하여 Abstract Syntax Tree를 생성하고 바이트코드로 변환하여 실행한다. 이때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다. 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합된다.
- 렌더 트리를 기반으로 HTML요소의 레이아웃(위치와 크기)을 계산하고 브라우저 화면에 HTML 요소를 페인팅한다.
www.google.com 입력하면 무슨일이 발생할까?
브라우저 렌더링에 필요한 리소스는 모두 서버에 존재한다. 따라서 브라우저는 필요한 리소스를 서버에게 요청하고 서버가 응답한 리소스를 파싱하여 렌더링한다.
그렇다면 브라우저는 어떻게 서버에게 리소스 요청을 보낼까? 우리가 브라우저 주소창에 입력하는 행위가 리소스 요청을 보내는 행위이다. 브라우저 주소창에 URL을 입력하고 엔터키를 누르면 URL의 호스트 이름이 DNS를 통해 IP주소로 변환되고 이 IP주소를 갖고있는 서버에게 요청을 전송한다.

보통 https://www.google.com 이렇게 입력을 하게되면 서버의 루트폴더에 있는 index.html 파일을 응답하게 된다.
만일 다른 정적파일을 서버에 요청하려면 호스트 뒤에 패스에 기술하여 서버에 요청하면 된다.
하지만 반드시 브라우저 주소창으로 정적 데이터를 요청하는 것만은 아니다. ajax를 통해 동적으로 서버에 데이터를 요청할 수도 있다.

하지만 루트요청(스킴과 호스트만으로 구성된 URI에 의한 요청)을 보내면 index.html 파일만 응답되어야하는데 엄청 많은 리소스들이 같이 딸려오는 것을 알 수 있다.
이것은 브라우저 렌더링 엔진이 index.html을 파싱하다가 css파일을 로드하는 link 태그, 자바스크립트를 로드하는 script 태그 등 외부 리소스를 로드하는 태그를 만나면 파싱이 일시 중단되고 해당 리소스 파일을 서버로 요청하기 때문이다.
렌더 트리와 리렌더링
HTML과 CSS를 파싱하여 각각 DOM, CSSOM 객체를 생성한다. 이는 문서를 브라우저가 이해할 수 있는 자료구조로 변환한 것이다. DOM과 CSSOM은 렌더링을 위한 렌더 트리로 결합된다. 렌더 트리는 렌더링을 위한 트리로 화면에 렌더링되지 않는 meta태그나 script태그, css의 display:none 등은 포함되지 않는다.
렌더 트리를 기반으로 각 HTML 요소의 위치와 크기 등을 결정하고 페인팅 작업을 거쳐 사용자가 화면으로 볼 수 있게 된다.
레이아웃 계산과 페인팅이 재차 실행되는 것을 리렌더링이라고 한다.
리렌더링 과정을 리플로우와 리페인트 과정으로 나눠 설명할 수 있다.
리플로우는 레이아웃 계산을 다시 하는 것이고 리페인트는 다시 결합된 렌더 트리를 기반으로 다시 페인팅을 하는 것이다.
빈번한 리플로우 과정은 성능에 악영향을 미칠 수 있다.
밑에 작성한 것들은 리렌더링을 발생시키므로 빈번하게 발생하지 않도록 주의해야한다.
- JS에 의한 노드 추가 또는 삭제
- 브라우저 창의 리사이징에 의한 뷰포트 크기 변경
- HTML 요소의 레이아웃을 변경시키는 스타일 변경
- width/height, margin, padding, border, display, position, top/right/bottom/left 등
레이아웃을 다시 계산하는 것은 리플로우
계산된 레이아웃으로 다시 페인팅하는 것을 리페인팅
두 과정을 합친 것은 리렌더링
script 태그의 위치
<html>
<head>
<title>Mission 1</title>
<meta charset="utf-8" />
<script src="app.js"></script>
</head>
<body>
<div id="todo-list"></div>
</body>
</html>
렌더링 엔진과 자바스크립트 엔진은 직렬적으로 파싱을 수행한다.
이게 무슨말이냐면 지금 위의 코드에서 HTML을 파싱하다가 script 태그를 만나면 HTML 파싱이 중단되고 자바스크립트 엔진이 자바스크립트 파일을 파싱한다. 즉, HTML 파싱이 블로킹된다.
이 경우에 발생할 수 있는 문제점은 DOM 요소가 생성되기 이전에 자바스크립트 코드에서 DOM을 조작하는 코드가 있다면 수행되지 않을 수 있다. 아직 HTML 파싱이 완료되지 않았기 때문이다.
<html>
<head>
<title>Mission 1</title>
<meta charset="utf-8" />
</head>
<body>
<div id="todo-list"></div>
<script src="app.js"></script>
</body>
</html>
따라서 HTML이 모두 파싱되고 DOM트리가 생성된 후에 자바스크립트 파일을 파싱하도록 body태그 맨 아래에 둔다.
이렇게 되면 자바스크립트 파일이 DOM요소가 생성되기 전에 조작해서 발생하는 오류를 막을 수 있고, 자바스크립트가 실행되기 전에 DOM을 완성해서 렌더링되므로 페이지 로딩 시간이 단축된다.
왜 script 태그를 밑에 두는지 이유를 알아야한다. 그렇게 하는것에는 이유가 있다.
script 태그의 async/defer 어트리뷰트
외부 자바스크립트를 사용하는 경우 script태그에 async 또는 defer 어트리뷰트를 줄 수 있다.
두 방식 모두 HTML 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행된다. 하지만 자바스크립트 실행 시점에 차이가 있다.
async는 HTML 파싱과 자바스크립트 파일 로드가 동시에 진행된다. 그러다가 자바스크립트 파일 로드가 완료되면 곧바로 자바스크립트가 실행되고 HTML 파싱이 중단된다.
defer 또한 HTML 파싱과 자바스크립트 파일 로드가 동시에 진행된다. 하지만 defer는 자바스크립트 파일 로드가 완료되어도 곧바로 실행되지 않고 DOM 생성이 완료될 때까지 기다린다. 그리고 DOM 생성이 완료되면 자바스크립트 파일을 파싱하고 실행한다.
'자바스크립트' 카테고리의 다른 글
DOM과 DOM API를 통한 동적 조작 (0) | 2024.03.27 |
---|---|
네이티브 객체 vs 호스트 객체 (0) | 2024.03.26 |
호이스팅 (0) | 2024.03.06 |
스코프 (0) | 2024.03.03 |
이벤트 위임 (0) | 2024.03.01 |