(<자바스크립트 셀프 QnA> 시리즈에 작성된 포스팅들은 각 주제에 해당하는 <모던 자바스크립트 딥다이브> 챕터를 읽으며 요약한 내용입니다. 더 자세한 내용은 <모던 자바스크립트 딥다이브>를 참고해주세요.)
NodeJS의 등장으로 자바스크립트는 웹 브라우저를 벗어나 서버 사이드 애플리케이션 개발에서도 사용할 수 있는 범용 개발언어가 되었다. 그러나 여전히 자바스크립트가 가장 많이 사용되는 분야는 웹 브라우저 환경에서 동작하는 웹페이지/애플리케이션의 클라이언트 사이드! 이때 보통 자바스크립트는 HTML, CSS와 함께 실행된다. 때문에 브라우저 환경을 잘 이해할 수록 효율적인 클라이언트 사이드 자바스크립트 프로그래밍이 가능하다!
Q1 파싱과 렌더링은 무엇인가요?
- 파싱: 프로그래밍 언어의 문법에 맞게 작성된 텍스트 문서를 읽어 들여 실행하기 위해 텍스트 문서의 문자열을 토큰으로 분해하고 토큰에 문법적 의미와 구조를 반영해 트리 구조의 자료구조인 파스 트리를 생성하는 일련의 과정
- 렌더링: HTML, CSS, 자바스크립트로 작성된 문서를 파싱하여 브라우저에 시각적으로 출력하는 것
Q2 브라우저 렌더링 과정을 간단히 요약해주세요.
- 1) 브라우저는 렌더링에 필요한 리소스를 요청하고 서버로부터 응답 받는다.
- 2) 브라우저 렌더링 엔진은 HTML을 파싱해 DOM, CSS를 파싱해 CSSOM을 생성하고 이들을 결합해 렌더 트리를 생성한다.
- 3) 자바스크립트를 파싱해 AST를 생성하고, 바이트 코드로 변환하여 실행한다. (이 때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다. 변경된 것들은 다시 렌더트리로 결합된다.
- 4) 렌더 트리를 기반으로 HTML 요소의 레이아웃을 계산하고 브라우저 화면서 HTML 요소를 페인팅한다.
Q3 브라우저의 요청과 응답
- 브라우저의 주 역할은 리소스를 서버에 요청하고 응답을 받아와 브라우저에 렌더링 하는 것!
- 서버에 요청을 전송하기 위해 브라우저에 주소창이 있다. 주소창에 url을 입력하면 url의 호스트 이름(도메인)이 DNS를 통해 IP 주소로 변환되고, 해당 IP 주소를 갖는 서버에게 요청 전송!
- 특정한 정적 리소스를 지정하지 않으면 일반적으로 서버는 루트 요청에 대해
index.html
을 응답하도록 설정되어 있다.
- ajax나 RestAPI를 통해서도 서버에 정적 파일을 요청할 수 있다.
- HTML 파싱 도중에 외부 리소스를 로드하는 태그,
link
img
script
태그 등을 만나면 HTML 파싱이 중단되고 해당 리소스 파일을 서버로 요청해 받아온다!
- 브라우저와 서버의 통신규약 HTTP! 다중 요청/응답이 불가한 HTTP1.1 대신 다중 요청/응답이 가능한 HTTP2.0을 사용하면 페이지 로드 속도가 약 50% 정도 빠르다.
Q4 HTML 파싱과 DOM 생성
- 브라우저 요청에 서버가 응답한 HTML 문서는 문자열로 이루어진 순수한 텍스트.
- 순수 텍스트를 렌더링하려면 브라우저가 이해할 수 있는 자료구조(객체)로 변환하여 메모리에 저장해야 한다.
- 브라우저의 렌더링 엔진은 HTML 문서를 파싱해 브라우저가 이해할 수 있는 자료구조인 DOM(Document Object Model)을 생성한다.
- DOM은 결국 HTML 문서를 파싱한 결과물이다.
- 서버는 브라우저가 요청한 HTML 파일을 읽어 메모리에 저장하고, 메모리에 저장된 바이트를 응답한다.
- 바이트 형태의 HTML 문서는 meta 태그의 charset 어트리뷰트에 의해 저장된 인코딩 방식을 기준으로 문자열로 변환된다.
- 문자열로 변환된 HTML 문서를 일겅들여 문법적 의미를 갖는 코드의 최소단위인 토큰들로 분해한다.
- 각 토큰들을 객체로 변환해 노드를 생성한다.
- HTML 요소 간의 부자 관계를 반영해 모든 노트들을 트리 자료구조로 구성한다. 이렇게 구성된 트리 자료구조를 DOM이라고 한다.
Q5 CSS 파싱과 CSSOM 생성
- HTML과 동일하게
바이트 > 문자 > 토큰 > 노드 > CSSOM
과정을 거쳐 생성된다.
- CSSOM은 CSS의 상속을 반영하여 생성된다.
Q6 렌더트리 생성
- 렌더링 엔진을 DOM과 CSSOM을 결합해 렌더 트리를 생성한다.
- 렌더 트리는 렌더링을 위한 트리 구조의 자료구조! 렌더 트리는 HTML 요소의 레이아웃을 계산하는데 사용되며 브라우저 화면에 픽셀을 렌더링 하는 페인팅 처리에 입력된다.
- 렌더 트리는 브라우저 화면에 렌더링 되는 노드 만으로 구성된다. (스크립트 태그나, display: none 같은 애들은 포함되지 않는다는 뜻!)
- 그러므로 자바스크립트에 노드 추가/삭제, 뷰포트 변경, HTML 요소의 레이아웃에 변경을 발생시키는 스타일 변경은 리렌더링을 일으키고, 이는 성능에 악영향을 주기 쉽다.
- 리렌더링이 빈번히 발생하지 않도록 프로그래밍을 하는 것이 중요하다.
Q7 자바스크립트 파싱과 실행
- 자바스크립트 파싱과 실행은 브라우저의 렌더링 엔진이 아닌 자바스크립트 엔진이 처리한다.
- 자바스크립트 엔진은 NodeJS의 V8, 파이어폭스의 SpiderMonkey, 사파리의 JavaScriptCore 등 다양한 종류가 있다. 모두 ECMAScript 사양을 준수한다.
Q8 리플로우와 리페인트
- 자바스크립트 코드에서 DOM이나 CSSOM을 변경하는 DOM API가 사용된 경우 DOM이나 CSSOM이 변경되어, 리플로우와 리페인트가 일어난다.
- 리플로우: 레이아웃 계산을 다시 함
- 리페인트: 재결합된 렌더 트리를 기반으로 다시 페인트를 하는 것
- 레이아웃에 변경이 없는 경우 리플로우 없이 리페인트만 실행된다.
Q9 자바스크립트 파싱에 의한 HTML 파싱 중단
- 렌더링 엔진과 자바스크립트 엔진은 병렬적으로 파싱을 실행하지 않고, 직렬적으로 파싱을 수행한다.
- 브라우저는 동기적으로 HTML, CSS, 자바스크립트를 파싱하고 실행한다.
script
태그의 위치에 따라 HTML 파싱이 블로킹되어 DOM 생성이 지연될 수 있다.
- 자바스크립트 코드에서 DOM이나 CSSOM을 변경하는 DOM API를 활용할 경우, 해당 DOM이나 CSSOM이 이미지 생성되어 있어야 한다.
- 그래서 보통 body의 가장 하단에 스크립트 태그를 위치시킨다.
- DOM이 완성되지 않은 상태에서 DOM을 조작해 에러를 발생시키는 상황을 피함과 동시에 자바스크립트의 로딩/파싱/실행으로 인해 HTML 요소들의 렌더링에 지장 받는 일이 발생하지 않아 페이지 로딩 시간이 단축된다.
Q10 script 태그의 async/defer 어트리뷰트
- 위 DOM 생성 중단 문제를 해결하기 위해 HTML5 부터 script 태그에 async와 defer 속성이 추가되었다.
- HTML 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행된다.
<script async src="extern.js"></script>
<script defer src="extern.js"></script>
- async: HTML 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행된다. 하지만 자바스크립트 파일이 로드되면 바로 자바스크립트 파싱과 실행이 진행되며 이때 HTML 파싱은 중단된다.
- defer: 위와 동일하게 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행된다. 하지만 자바스크립트의 파싱과 실행은 HTML 파싱이 완료된 직후에 진행된다.