티스토리 뷰
공부 시작 배경
그동안 웹 프론트엔드 개발을 하면서 결과물을 보여주는
웹 브라우저가 어떻게 동작하는지, 그 원리에 대해서 무심하게 지나치는 경우가 많았는데요.
제가 이 일을 하면서 "원리조차 모르고 있으면서 제대로 된 개발을 할 수 또는 하고 있을까?"
라는 생각이 들 때즘 "차근차근 공부해 나가야겠다" 라는 생각을 했습니다.
공부는 " 모던 자바스크립트 Deep Dive "라는 책을 바탕으로 진행하였습니다.
브라우저의 렌더링 과정
브라우저는 간단하게는 HTML, CSS, 자바스크립트 문서를 파싱하여 브라우저에 렌더링하는 순서대로
브라우저에 페이지를 띄워줍니다. 좀 더 세부적으로 과정을 살펴보자면 다음과 같은 순서입니다.
1. 브라우저의 렌더링에 필요한 리소스인 HTML, CSS, Javascript, 이미지, 폰트 파일 등을 요청하고 서버로부터 응답을 받습니다.
2. 서버로부터 응답된 리소스인 HTML, CSS 문서를 브라우저의 렌더링 엔진은 파싱하여 DOM과 CSSOM을 생성하고 이 둘을 서로 매칭 시킬 수 있는 렌더 트리를 생성합니다.
3. 브라우저의 자바스크립트 엔진은 서버로부터 응답된 리소스인 Javascript를 파싱해서 AST(Abstract Syntax Tree)를 생성하고 바이트코드로 변환하여 실행합니다. 이때 자바스크립트는 DOM API로 DOM이나 CSSOM을 변경할 수 있고 변경되게 되면 다시 렌더 트리로 결합됩니다.
4. 렌더 트리로 파싱된 HTML과 CSS 들이 결합된 요소들은 레이아웃을 계산해서 브라우저 화면에 페인팅(Painting)합니다.
아래 그림은 제가 위의 과정을 보고 이해한 토대로 그려본 간단한 브라우저 렌더링 과정입니다.
서버로 부터 요청(requeset)과 응답(response)
렌더링에 필요한 리소스들을 요청하고 응답 받는다고 했는데, 그 과정은 어떻게 이루어질까요?
먼저 브라우저의 핵심 기능에 대해서 설명하자면, 필요한 리소스들을 서버에 요청하고 응답 받은 정보를 토대로
브라우저에 시각적으로 렌더링하는 것입니다.
브라우저의 사용자 인터페이스 요소
요청의 과정을 설명하기 앞서 브라우저의 일반적인 사용자 인터페이스 요소를 살펴보겠습니다.
브라우저의 사용자 인터페이스 요소를 살펴보면 일반적으로 다음과 같이 구성되어 있습니다.
- URI를 입력하는 주소 표시 줄
- 이전 / 다음 버튼
- 북마크
- 새로고침, 현재의 문서의 로드를 중단할 수 있는 정지 버튼
사용자 인터페이스 요소중 주소 표시줄 요소를 통해서 서버에 요청을 하게됩니다.
브라우저의 주소창에 URL을 입력하고 엔터 키를 누르거나 검색 버튼을 클릭하면 입력한 URL의 호스트 이름이 DNS를 통해서 IP 주소로 변환이 되고 이 IP 주소를 가지고 있는 서버에게 요청을 전송합니다.
더 나아가서 주소창에 URL을 입력하면 어떤 과정을 거치게 될까요?
브라우저에 https://www.naver.com 를 입력해서 요청하게되면 루트 요청이 naver.com 서버로 전송됩니다.
여기서 루트 요청이란 /, Scheme, Host 만으로 구성된 URI에 의한 요청을 말합니다.
위 네이버 루트 요청에서 따로 요청하는 리소스를 명시해주지 않았지만 서버는 일반적으로 index.html을 암묵적으로 응답하도록 되어있습니다.
https://www.naver.com 로 요청하면 암묵적으로 https://www.naver.com/index.html 으로 요청을 하게 되는것이죠.
서버는 루트 폴더에 존재하는 정적 파일인 index.html을 클라이언트에게 응답합니다.
서버는 이렇게 index.html 파일만 요청할 수 있는것은 아닙니다. 다른 정적 파일을 주소를 통해 접근이 가능하며,
자바스크립트를 통해서 정적 / 동적 데이터를 요청할 수 있습니다.
이 과정을 눈으로 볼 수 있는 방법이 있습니다.
각 브라우저의 개발자 도구의 Network 패널을 통해 확인이 가능한데요, https://www.naver.com 과 같이 브라우저 주소창을 입력하기 전 Network 패널을 띄워놓고 입력하면 다음과 같이 확인할 수 있습니다.
먼저 www.naver.com 에 해당하는 index.html 리소스를 받고 렌더링에 필요한 리소스들 ( CSS, Javascript, 이미지, 폰트 파일 등등 )이 응답되었습니다.
index.html 만 요청했지만 다른 리소스들도 응답을 받은 이유는 브라우저 렌더링 엔진이 받은 HTML파일인 index.html 파일을 파싱하는 도중에 외부 리소스들을 로드하는 태그들을 만났기 때문입니다.
예를들면 CSS 파일 로드하는 link 태그, 이미지 파일 로드하는 img 태그, Javascript 파일 로드하는 script 태그 등이 있습니다.
해당 태그들을 HTML 문서를 파싱하는 동안 만나게되면 파싱을 일시 중단하고 만난 태그의 적절한 리소스 파일을 서버로 요청합니다.
HTML 파싱 그리고 DOM 생성
우리가 페이지를 만들기 위해 작성한 HTML 문서는 문자열인 순수한 텍스트입니다.
우리가 작성하는 <div>, <p> 등등 과 같은 태그들과 여러 코드들을 브라우저에 시각적으로 보여주기 위해서는
문서를 브라우저가 이해할 수 있는 자료 구조로 변환하여 메모리에 저장해야합니다.
자료구조를 효과적으로 저장하기 위해서는 객체가 사용됩니다.
브라우저의 렌더링 엔진은 HTML 문서를 응답받으면 HTML 문서를 파싱해서 브라우저가 이해할 수 있는 자료구조로 만드는 과정을 거쳐 DOM(Document Object Model)을 만들어냅니다.
1. 브라우저를 통해 서버에 HTML 파일을 요청합니다.
2. HTML 파일을 읽으면, 메모리에 저장하고, 저장된 바이트(2진수, 예 : 101110110111......)를 인터넷을 경유하여 응답합니다.
3. 브라우저는 요청한 HTML 문서를 바이트(2진수) 형태로 응답받으며, meta 태그의 charset 어트리뷰트에 작성된 인코딩(예 : UTF-8) 방식을 기준으로 문자열로 변환됩니다.
- meta 태그의 charset 어트리뷰트에 선언된 인코딩 방식은 content-type: text/html, charset=utf-8 과 같이 응답 헤더( response header )에 담겨 응답되며 브라우저는 이 인코딩 방식을 기준으로 문자열로 변환해줍니다.
4. 문자열로 변환된 HTML 문서를 문법적으로 의미를 가지고 있는 토큰( token )들로 분해합니다.
- 토큰은 다음과 같이 객체 형태로 만들어지게 되는데 시작 태그와 종료 태그 콘텐츠 등으로 구성되어 있습니다.
{
startTag: 'html',
contents: {
startTag: 'head',
contents: {...},
...
},
endTag:'html'
}
- 토큰화 알고리즘
HTML 토큰을 생성하기 위해서는 알고리즘을 거칩니다.
다음은 예시를 통한 토큰화 과정입니다.
<html>
<body>
Hello world
</body>
</html>
A. 토큰의 발행 ( < 태그를 만나고 > 태그를 만나기 까지 )
1. 초기 상태는 "자료 상태" 입니다.
2. < 문자를 만나면 상태는 "태그 열림 상태"로 변합니다.
3. a 부터 z까지의 문자를 만나면 "시작 태그 토큰"을 생성하고 "태그 이름 상태"로 변합니다.
4. 각 만난 문자에는 새로운 토큰 이름이 붇게됩니다. 이때 생성된 토큰은 HTML 토큰입니다.
5. > 문자를 만나게 되면 현재 토큰이 발행이 되고 상태는 다시 초기 상태였던 "자료 상태"로 바뀌게 됩니다.
B. 태그의 동일 절차 토큰 발행
자료를 다 읽기 전까지 A의 동일한 절차는 반복됩니다.
지금까지 태그 html과 body 태그의 토큰을 발행하고 "자료 상태"라고 한다면,
그다음 Hello World의 문자에서 H 문자를 만나면 문자 토큰이 생성되고 발행이 될것입니다.
이것 또한 종료 태그의 < 문자를 만날 때 까지 진행되며,
Hello World의 각 문자를 위한 문자 토큰을 발행합니다.
C. / 문자를 통한 종료 태크 토큰 생성
종료 태그는 / 문자를 통해서 판별하게 됩니다.
< 를 발견하면 "태그 열림 상태"로 변경되고 / 문자를 만나게 되면 "종료 태그 토큰"을 생성하고
위에서 토큰의 발행 3번에서 설명한 "태그 이름 상태"로 변경되게 됩니다.
이 상태는 자료 상태가 되기전의 상태인 > 문자를 만날때까지 유지되며,
> 문자를 만나게 되면 새로운 태그 토큰이 발행이 되고 다시 "자료 상태"로 돌아가게 됩니다.
5. 분해된 각 토큰들을 객체로 변환하여 노드(node)를 생성하는데, 이는 토큰의 내용에 따라서 문서 노드, 요소 노드, 어트리뷰트 노드, 텍스트 노드가 생성이되며 DOM을 구성하는 기본 요소입니다.
6. HTML 문서는 HTML 요소들이 모여서 구성되고 이 요소들은 부모요소, 자식요소들의 관계로써 형성이 됩니다.
토큰화 과정을 통해서 생성된 노드들은 중첩되며, 부모와 자식관계로써 트리 자료 구조를 구성하고 이를 DOM( Document Object Model) 이라고 부릅니다.
CSS 파싱, 그리고 CSSOM 생성
브라우저의 렌더링 엔진은 HTML 문서를 위에서 부터 차근차근 한 줄씩 파싱해나가며 DOM을 생성합니다.
한 줄씩 파싱하던 도중 CSS를 로드하는 link 태그나 style 태그를 만나면 DOM 생성을 일시적으로 중지합니다.
link 태그에는 외부 리소스를 받아올 수 있도록 href 어트리뷰트를 제공해주는데, 이러한 과정으로 서버에 요청해서 CSS 리소스를 받거나, style 태그를 만나게되면 파싱 과정을 거치며 CSSOM( CSS Object Model ) 을 생성합니다.
CSS 로드가 시작되고 DOM 생성이 일시적으로 중단된 시점에서 CSSOM의 생성이 완료되면 다시 HTML 을 파싱하며 DOM을 생성합니다.
ul 에 css를 적용하게 되면 li 요소들도 상속 관계에 의해서 li 에 반영이 되며 CSSOM이 생성되게 됩니다.
ul {
list-style:none;
font-size:20px;
}
렌더 트리(render tree) 생성
지금까지 브라우저의 렌더링 엔진이 서버로부터 응답받은 HTML 파일을 파싱해서 DOM과 CSSOM을 생성한 과정을 살펴보았습니다. 파싱된 DOM 과 CSSOM은 브라우저에 렌더링을 하기위해서 렌더 트리(render tree)로 결합됩니다.
렌더트리는 렌더링을 위한 자료 구조라서 브라우저 화면에 렌더링되지 않는 노드( meta 태그, script 태그 등 )와 CSS에 의해서 비표시( display:none 등 ) 되는 노드들을 포함하지 않으며 렌더링이 되는 노드들로만 구성이됩니다.
DOM과 CSSOM의 결합으로 생성된 렌더트리는 HTML 요소들의 레이아웃을 계산하고 브라우저 화면에 픽셀을 렌더링하는 페인팅(painting) 처리에 입력이 됩니다.
브라우저의 렌더링 과정은 반복해서 실행될 수 있습니다.
예시로 다음과 같은 상황에서 반복해서 레이아웃 계산, 페인팅이 실행됩니다.
- 자바스크립트로 인한 노드(node) 추가, 삭제
- 브라우저 창의 리사이징으로 인한 뷰포트(viewport) 크기 변경
- HTML 요소의 레이아웃 변경을 발생시키는 스타일 변경 ( width/ height, margin, padding 등 )
이 과정을 통한 리렌더링은 성능에 악영향을 주어 빈번하게 발생하지 않도록 주의해야 합니다.
자바스크립트 파싱과 실행
DOM은 HTML 문서를 구성하는것 말고도 HTML 요소와 스타일을 변경할 수 있도록 DOM API를 제공합니다.
이 DOM API를 통해서 DOM을 제어하여 동적으로 조작이 가능합니다.
이전의 HTML 문서를 순차적으로 한 줄씩 파싱하는 과정에서 CSS로드를 해서 link 태그나 style 태그를 만나면 DOM 생성을 위한 HTML 파싱을 일시적으로 중단하고 CSS 파싱을 통한 CSSOM을 생성한다고 했던 부분을 기억하나요?
이와 마찬가지로 HTML 문서를 파싱하다가 자바스크립트 로드를 위한 script 태그를 만나면 DOM 생성을 일시적으로 중단합니다.
script 태그는 src 어트리뷰트를 통해 서버로 부터 자바스크립트 리소스를 요청 및 응답받을 수 있고, 자바스크립트 코드를 콘텐츠로 작성할 수 있습니다. 자바스크립트 코드를 만나게 되면 파싱을 시작하게 되는데, 이때 자바스크립트의 파싱은 브라우저 렌더링 엔진이 아닌 자바스크립트 엔진이 처리합니다.
자바스크립트의 파싱과 실행은 자바스크립트 엔진이 제어권을 넘겨받아 처리하는데 파싱 과정에서 CPU가 이해할 수 있도록 저수준 언어( low-level language )로 변환하고 실행합니다.
자바스크립트 엔진은 여러 종류가 있으며, 모든 자바스크립트 엔진은 ECMAScript 사양을 준수합니다.
자바스크립트 엔진은 DOM, CSSOM과 같이 AST(Abstract Syntax Tree / 추상적 구문 트리)를 생성합니다.
생성된 AST로 인터프리터(자바스크립 코드를 해석하고 실행시키는 해석기 )가 실행할 수 있는 중간 코드( intermediate code )인 바이트코드를 생성하여 실행합니다.
자바스크립트 파싱 및 실행 과정은 다음과 같습니다.
토크나이징(tokenizing)
HTML 문서와 같이 단순한 문자열인 자바스크립트 소스코드를 어휘 분석(lexical analysis)해서 문법적으로 의미를 가지는 코드의 최소 단위 토큰(token)들로 분해하는 과정입니다.
파싱(parsing)
분해된 토큰들을 구문 분석(syntactic analysis)하여 AST(Abstract Syntax Tree, 추상적 구문 트리)를 생성합니다.
AST는 토큰에 문법적 의미와 구조를 반영한 트리 구조의 자료구조입니다.
* AST를 사용하면 Typescript, Babel 등과 같은 트랜스파일러(transpiler)를 구현할 수도 있다고 합니다.
바이트코드 생성과 실행
파싱으로 생성된 AST는 자바스크립트 코드를 해석하고 실행시킬 수 있는 인터프리터가 실행할 수 있는 중간 코드인 바이트코드로 변환되며 인터프리터에 의해 실행됩니다.
* 어휘 분석과 구문 분석이란?
어휘 분석
어휘 분석은 자료를 *토큰으로 분해하는 과정입니다.
토큰은 유효하게 구성된 단위의 집합체이며,
인간의 언어로 풀면 사전에 있는 모든 단어로 설명할 수 있습니다.
어휘 분석기는 공백과 줄 바꿈과 같은 의미 없는 문자를 제거하는 일을 하기도 합니다.
* 토큰은 토큰이름과 속성 값으로 구성되는 데이터입니다.
각 토큰은 토큰의 패턴에 적절한 어휘항목을 지니게 됩니다.
구문 분석
언어의 구문 규칙을 적용하는 과정을 구문 분석이라고 합니다.
리플로우(reflow)와 리페인트(repaint)
위에서 말했던 빈번한 리렌더링이 발생되면 성능이 저하된다는 말을 기억하시나요?
빈번하게 DOM과 CSSOM이 변경되고 레이아웃이 계산되고, 화면에 페인트해주는 과정이 발생하게 되면 성능이 저하되는데요.
자바스크립트 코드를 통해서 DOM과 CSSOM을 변경시킬수 있는 DOM API를 사용하게 되면 ( 예 : domEl.style.??? ) DOM과 CSSOM이 변경되며 다시 렌더트리로 결합되고 레이아웃 계산과 페인트의 과정을 거쳐서 다시 화면에 리렌더링을 시켜줍니다. 레이아웃 계산과정을 리플로우(reflow), 렌더 트리를 기반으로 다시 페인트하는 과정을 리페인트(repaint) 라고 합니다.
항상 리플로우가 발생하고 리페인트가 발생되는 순차적인 구조로 진행되지는 않습니다. 레이아웃에는 영향이 없고 렌더트리만 변경이되어 페인트하는 과정만 진행된다면 별도의 리플로우의 단계없이 리페인트 과정만 실행됩니다.
자바스크립트 파싱에 의한 HTML 파싱 중단 및 동작하지 않는 DOM API
HTML 문서를 읽어들이고 파싱하는 과정은 위에서부터 아래로 순차적인 구조로 파싱합니다.
그리고 그 과정에서 CSS를 로드하는 태그나, 자바스크립트를 로드하는 태그를 만나게되면 HTML 파싱을 일시적으로 중단하고 각각의 리소스들을 파싱하게되는데요, 이때 자바스크립트 로드 태그의 위치에 따라, 어트리뷰트에 따라서 자바스크립트에 DOM또는 CSS를 제어하는 구문을 실행하고자 할때 문제가 발생할 수도 있습니다.
script태그의 위치는 매우 중요한데, 브라우저는 동기적으로 파싱을 수행하다가 아래와 같이 script 태그를 만나게되면 HTML 파싱이 블로킹되고 DOM 생성에 지연을 가지게 됩니다. 이것은 자바스크립트 파일의 용량이 커 자바스크립트의 파싱과 실행 과정이 오래걸린다면 HTML 페이지의 렌더링이 오래걸릴 수 있다는 것을 의미합니다. 또한 위에서 말한 DOM API를 통해 DOM과 CSS를 변경하게 될때에 영향을 미치게됩니다.
바로 아래와 같은 상황에서 문제가 발생할 수 있습니다.
HTML 문서를 파싱하다 script태그를 만나게되고, 자바스크립트를 파싱하고 실행하는 과정에서 document.getElementById를 통해서 id가 apple인 HTML 요소를 가져오고 style을 color을 "#ff0000"로 바꾸려고합니다. 하지만 script태그보다 아래쪽에 있는 apple이란 id를 가진 HTML 요소가 아직 파싱이 되지 않았기때문에 정상적으로 실행이 되지 않습니다.
위의 코드를 브라우저 콘솔창을 통해서 보면 아래와 같은 에러를 표시하고있습니다.
이유는 HTML 요소를 불러오는 DOM API를 사용할때 제대로 요소를 불러오지 못한다면 null이 값으로 반환되기 때문인데요. 불러온 $appleElement의 값을 console.log 로 확인해보면 다음과 같습니다.
그렇기 때문에 해당 값이 객체도 아닐뿐더러 style이라는 프로퍼티가 없으니 에러가 나고 있었던 것입니다.
다시한번 정리하지만 위와 같은 상황에서는 아직 해당 HTML 요소가 파싱이 되지 않았기 때문에 DOM으로써 DOM API를 사용할때 문제가 발생한 것입니다.
위와 같은 상황을 해결하려면 다음과 같이 body의 최하단에 script태그를 작성해주면 됩니다.
위와 같이 script를 최하단에 작성하면 정상적으로 자바스크립트가 실행되는것을 확인할 수 있습니다.
body의 최하단에 script 태그를 작성하게 되면 HTML의 파싱의 일시적 중단으로 DOM이 만들어지지 않아 DOM API를 통해 조작하려하면 에러가 나는 상황 뿐만아니라, HTML 블로킹, 즉 script태그를 만나 자바스크립트의 로딩, 파싱, 실행으로 인해서 HTML 요소들이 렌더링에 지장받는 일이 줄어들어 페이지 로딩시간이 줄어들게 됩니다.
script 태그의 async, defer 어트리뷰트
위의 body의 최하단에 script 태그를 두어서 DOM API 제어의 에러와, 페이지 로딩시간을 단축시키는 행위들을 보았습니다. 하지만 이 방법은 완벽한 해결책이 아닙니다. 파싱할 HTML 문서가 엄청나게 길게되면 자바스크립트의 파싱도 그만큼 그 후에 일어나게 될것입니다.
이 문제를 해결하기 위해서 HTML5부터 script 태그에 async와 defer 어트리뷰트가 추가되었습니다.
async와 defer의 사용법은 우선 외부의 자바스크립트 소스를 받을 수 있는 src 속성이 있어야만 사용이 가능합니다.
// async와 defer의 사용예제
<script async src="./script/long.js"></script>
<script defer src="./script/small.js"></script>
async와 defer을 사용하면 HTML파싱이 되기전 상단에 script 태그를 두어도 DOM API의 에러를 해결할 수 있을 뿐더러,
HTML 파싱이 일시적으로 중단되어 페이지의 로드가 늦어지는 블로킹 현상도 방지할 수 있습니다.
async와 defer은 비동기적으로 HTML 파싱과 script의 src 어트리뷰트를 통해 가져온 자바스크립트 파일의 로드, 파싱, 실행이 비동기적으로 동시에 진행이 되는데, 이 두 어트리뷰트의 차이점은 자바스크립트의 실행 시점입니다.
async
async 는 HTML 파싱과 자바스크립트 파일의 로드가 비동기적으로 동시에 진행됩니다.
defer과의 차이점이라고하면, 파일의 로드가 완료되면 파싱후 실행이됩니다.
여러개의 script 태그에 async 어트리뷰트가 있다면, 실행 시점은 로드가 완료된 시점이라 자바스크립트 실행의 순서를 보장받을 수 없습니다.
다음과 같은 예시를 봅니다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="./style/style.css">
<script src="./script/long.js" async></script>
<script src="./script/small.js" async></script>
</head>
<body>
<ul>
<li id="apple">Apple</li>
</ul>
</body>
</html>
// small.js
document.addEventListener("DOMContentLoaded", () => {
const $appleElement2 = document.getElementById("apple");
$appleElement2.style.color = "#00ffff";
});
// long.js
document.addEventListener("DOMContentLoaded", () => {
// ..... 많은 소스코드의 양
const $appleElement = document.getElementById("apple");
$appleElement.style.color = "#ff0000";
});
소스코드의 용량이 큰 long.js 파일을 로드하고 그 후에 small.js를 로드하는 순서입니다.
하지만 위의 코드는 다음과 같은 상황이 나옵니다.
분명 small.js 의 "#00ffff" 색상이 들어가야하는데 간혹가다 long.js의 "#ff0000"색상이 들어갈때가 있습니다.
위의 상황처럼 async는 자바스크립트의 로드 시점에 따라 순서를 보장받을 수 없습니다.
순서와 상관없이 로드가 먼저 되는대로 실행되어야 하는 파일일때 async 어트리뷰트를 사용하면 효율적일 수 있습니다.
defer
순서를 보장받을 수 없는 async대신 defer은 작성 순서를 보장받을 수 있습니다.
defer 어트리뷰트가 있다면 HTML 파싱과 자바스크립트 로드가 비동기적으로 수행되고 HTML 파싱이 완료된 직후
자바스크립트의 파싱과 실행이 진행됩니다. 이때 DOMContentLoaded 이벤트가 발생하며 안전하게 DOM API를 사용해 DOM을 제어하기 위해서는 DOMContentLoaded 이벤트 콜백함수 내부에 로직을 작성해주면 됩니다.
정리해볼까요?
Q.브라우저 렌더링 과정에 대해서 설명해주세요.
A. 먼저 브라우저 주소창에 URL을 입력하면 서버로부터 HTML 파일을 응답받게되고 HTML 파일을 파싱하며
CSS, 자바스크립트, 이미지, 폰트 등 로드 태그들을 만나면 HTML 파싱은 일시적으로 중단하고 해당 파일들을 파싱합니다. 그 과정에서 HTML과 CSS를 파싱하면 각각 DOM, CSSOM이 생성되는데 두개가 결합되어 렌더 트리가 생성이 됩니다.
렌더트리 생성후 HTML요소의 레이아웃 계산후 브라우저 화면에 페인팅을 하면서 렌더링이 되는데 이 과정 전에 자바스크립트 파일의 로드가 있었다면 자바스크립트의 파싱과정이 이루어지고 파싱된 자바스크립트는 AST라는 추상적 구문 트리 생성 바이트 코드로 변환후 인터프리터에 의해서 실행됩니다. 이때 DOM을 변경하는 DOM API가 실행이 되었다면 리플로우, 리페인트가 일어나게되는데 DOM과 CSSOM이 변경이 되고 레이아웃의 계산이 이루어지고 브라우저에 페인트가 되는 과정이 다시 실행되며 리렌더링이 일어날 수 있습니다.
Q. HTML 파싱중 자바스크립트 파일을 만나게돼서 페이지 로드가 느려지는 현상을 방지하기 위해서는 어떻게 해야할까요?
A. script 태그에서 외부 자바스크립트를 로드하기 위해 src 어트리뷰트를 사용하면 async, defer 어트리뷰트를 사용할 수 있습니다. 해당 어트리뷰트의 공통적인 기능으로는 HTML 파싱이 중단되지 않고 자바스크립트를 로드할 수 있고 로드가 되면 실행시킬 수 있다는 것인데요. 두가지의 차이점으로는 실행 순서입니다. async는 script태그의 순서에 상관없이 해당 소스의 로드가 끝나면 파싱하고 실행해서 순서의 보장을 받을 수 없는 반면, defer은 로드가 끝나고 HTML의 파싱이 끝나는 시점에 script 태그의 작성 순서대로 실행합니다. 이 두 속성을 적시적소에 사용하게되면 HTML의 파싱이 자바스크립트를 로드하는 것때문에 페이지 로드가 느려지게 되는 현상을 완화시킬 수 있습니다.
마무리...
평소에 등한시하던 브라우저 렌더링 과정에 대해서 공부를 해봤습니다.
추상적으로 파싱과 각각의 요소의 생성 그리고 실행까지의 여러 단계에 걸쳐서 공부를 해보았는데요.
추상적이였지만, 실행순서가 어떻게되는지를 파악할 수 있어 도움이 되었습니다.
출처