해당 포스팅은 위키북스의 모던 자바스크립트 Deep Dive라는 책을 독학하며 기록하는 글입니다.

브라우저에서 우리의 웹 페이지를 어떻게 렌더링하여 보여주는지 그 과정을 한 번 세세히 살펴보자.

먼저 파싱과 렌더링이 무엇인지 알아야 한다.

파싱(parsing)

파싱은 프로그래밍 언어의 문법에 맞게 작성된 텍스트 문서를 읽어 실행하기 위해 텍스트 문서의 문자열을 토큰으로 분해하고, 토큰의 문법적 의미와 구조를 반영하여 트리 구조의 자료구조인 파스 트리를 생성하는 일련의 과정을 말한다.

렌더링(rendering)

렌더링은 HTML, CSS, 자바스크립트로 작성된 문서를 파싱하여 브라우저에 시각적으로 출력하는 것을 말한다. 즉 HTML, CSS, 자바스크립트로 작성된 우리의 파일을 브라우저에 시각적으로 보여주는 것을 말한다.

브라우저 렌더링 과정

  1. 브라우저는 HTML, CSS, 자바스트립트, 이미지, 폰트 등 렌더링에 필요한 리소스를 서버에 요청하고 응답을 받는다.

  2. 브라우저 렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 통해 DOM과 CSSOM을 생성하고 이들을 결합하여 렌더 트리를 생성한다.

  3. 여기에 서버로부터 응답된 자바스크립트를 파싱하여 AST를 생성하고 바이트코드로 변환하여 실행한다. 이때 자바스크립트는 DOM이나 CSSOM를 변경할 수 있으며, 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합된다.

  4. 렌더 트리를 기반으로 HTML 요소의 레이아웃을 계산하고 브라우저 화면에 HTML 요소를 페인팅한다.

HTML 파싱과 DOM

서버에게 요청해 전달받은 HTML 파일은 문자열로 이루어진 순수한 텍스트이다. 이러한 텍스트 파일을 토큰들로 분해를 하고, 각 토큰들을 객체로 변환하여 노드들을 생성하는데 이렇게 생긴 노드들은 나중에 DOM을 구성하는 기본 요소가 된다. HTML 요소는 중첩 관계를 갖는데 이러한 중첩 관계 떄문에 부자 관계가 형성이 되고, 이는 곧 트리 자료구조로 구성이 된다. 이렇게 노드들로 구성된 트리 자료구조를 DOM(Document Objecet Model)이라고 한다.

다음과 같은 HTML파일이 있다고 할 때 생성되는 DOM 트리를 살펴보자.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <ul>
      <li id="apple">Apple</li>
    </ul>
    <script src="app.js"></script>
  </body>
</html>

CSS 파싱과 CSSOM

HTML 파일을 위와 같은 과정을 통해 파싱을 하는 도중 link태그를 통해 요청된 CSS파일이나 style태그를 통해 CSS 스타일을 지정하는 부분을 만나게 되면 해당 CSS내용은 위의 HTML파일과 같은 일련의 과정(바이트 -> 문자 -> 토큰 -> 노드 -> CSSOM)을 거친다. CSSOM을 완성하고 나면 HTML파일의 이후 부분을 이어서 파싱해 나가면서 DOM생성을 재개한다.

위의 HTML 코드에 포함된 style.css파일이 다음과 같을 때 생성되는 CSSOM트리를 살펴보자.

body {
  font-size: 18px;
}
ul {
  list-style-type: none;
}

생성된 CSSOM 트리를 살펴보면 분명 CSS 파일에서는 body태그와 ul태그에만 스타일을 지정했지만 body에서 지정한 스타일이 ul태그와 li태그에 모두 들어가 있는 것을 볼 수 있고, ul태그에서 지정한 스타일이 li태그에도 있는 것을 볼 수 있다. 이는 CSS 스타일이 그 아래 노드들에게 상속되었기 때문이다. 이렇듯 CSS에는 상속되는 스타일이 있고 상속이 안되는 스타일이 있다.

렌더 트리 생성

DOM트리와 CSSOM트리가 생성이 되어었다면 이제 둘을 합쳐 렌더 트리를 만들게 된다. 이때 사용자에게 보이지 않는 script태그나 meta태그같은 내용은 생략이 되고 생성이 되는데 위의 두 트리가 합쳐지면 어떤 렌더 트리가 생성되는지 살펴보자.

이렇게 사용자가 화면을 보는데 시각적으로 인식되지 않는 head태그와 그 아래 태그 및 script태그들은 생략되고 실제로 브라우저 화면에 그려지는 body영역만 렌더 트리에 포함된 것을 볼 수 있다. 추가적으로 CSS의 display: none속성을 통해 비표시되는 노드들도 생략된다.

완성된 렌더 트리는 각 HTML 요소의 레이아웃을 계산하는데 사용되며 브라우저 화면에 픽셀을 렌더링하는 페인팅 처리에 입력된다. 결국 브라우저 렌더링은 다음과 같은 일련의 큼직큼직한 과정을 거친다.

그리고 자바스크립트에 의한 노드 추가 또는 삭제, 브라우저 창의 리사이징에 위한 뷰포트 크기 변경, HTML 요소의 레이아웃의 변경을 발생시키는 스타일 변경이 일어날 때 마다 위의 렌더링 과정이 반복해서 실행된다.

리플로우와 리페인트

위에서 말했듯이 HTML 요소릐 레이아웃이나 스타일이 변경되는 몇 가지 상황이 발생하면 브라우저 화면에 렌더링이 다시 일어나는데 이를 리플로우, 리페인트라고 한다.

좀 더 엄밀히 말하면 레이아웃이 변경되어 각 HTML 요소들의 레이아웃 계산을 다시하는 것을 리플로우, 재결합된 렌더 트리를 다시 페이트하는 것을 리페인트라고 한다.

둘은 반드시 순차적을 동시에 실행되는 것은 아니며, 레이아웃의 변화가 없는 변경인 경우 리플로우없이 리페인트만 일어나기도 한다.

브라우저 렌더링 과정에서의 JS 파싱

HTML 파일을 파싱하던 도중 link태그나 style태그를 통해 CSSOM을 생성하여 하는 경우 HTML 파일의 파싱이 중단되고 이후에 재개된다고 했다. 이는 script태그를 통해 자바스크립트 파일을 만나게 될 때도 동일한데 이 경우 아직 DOM이 완성되지 않은 상태에서 자바스크립트 파일에서 DOM을 조작해 오류를 발생시킬 수도 있다.

이러한 오류를 방지하지 위해 script태그의 위치는 중요한데 보통 body태그가 끝나기 직전에 위치시키거나 head태그 안에 위치시키되 몇 가지 회피책을 사용한다.

  1. body태그가 끝나기 직전에 위치
    이 경우 이미 DOM 트리와 CSSOM 트리가 완성된 상태이기 때문에 script파일을 로딩하고 파싱 및 실행시켜도 오류가 나지 않는다.

  2. defer 어트리뷰트 사용
    script태그에 defer어트리뷰트를 사용하는 경우 이전과 동일하게 script태그를 만나면 자바스크립트 파일을 로드하지만 실행되지는 않고, HTML 파싱이 완료된 직후, 즉 DOM 생성이 완료된 직후에 실행된다.

  3. async 어트리뷰트 사용
    script태그에 async어트리뷰트를 사용하는 경우 이전과 동일하게 script태그를 만나면 자바스크립트 파일을 로드하지만 바로 실행되지는 않고, 로드가 되는 동안는 이어서 HTML 파싱이 진행된다. 하지만 자바스크립트 파일 로드가 완료된 직후 파일이 실행된다. 만약 자바스크립트 파일이 여러 개인 경우 실행 순서를 보장하지 않고 먼저 로드되는 파일을 먼저 실행시키기 때문에 순차적인 실행이 필요한 경우에는 권장되지 않는다.

profile
I Will be Relaxed Person

0개의 댓글