참고, 『모던 자바스크립트 (Deep Dive)』
브라우저의 주요 기능은 사용자가 선택한 자원을 서버에 요청하고 브라우저에 표시하는 것이다.
브라우저는 HTML과 CSS 명세에 따라 HTML 파일을 해석해서 표시하는데 이 명세는 웹 표준화 기구인 W3C(World Wide Web Consortium)에서 정한다.
사용자 인터페이스 - 주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등. 요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분이다.
브라우저 엔진 - 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어.
렌더링 엔진 - 요청한 콘텐츠를 표시. 예를 들어 HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시함.
통신 - HTTP 요청과 같은 네트워크 호출에 사용됨. 이것은 플랫폼 독립적인 인터페이스이고 각 플랫폼 하부에서 실행됨.
UI 백엔드 - 콤보 박스와 창 같은 기본적인 장치를 그림. 플랫폼에서 명시하지 않은 일반적인 인터페이스로서, OS 사용자 인터페이스 체계를 사용.
자바스크립트 해석기 - 자바스크립트 코드를 해석하고 실행.
자료 저장소 - 이 부분은 자료를 저장하는 계층이다. 쿠키를 저장하는 것과 같이 모든 종류의 자원을 하드 디스크에 저장할 필요가 있다. HTML5 명세에는 브라우저가 지원하는 '웹 데이터 베이스'가 정의되어 있다.
브라우저는 HTML, CSS, 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답을 받는다.
브라우저의 렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 파싱하여 DOM과 CSSOM을 생성하고 이들을 결합하여 렌더 트리를 생성한다.
브라우저의 자바스크립트 엔지는 서버로부터 응답된 자바스크립트를 파싱하여 AST(Abstract Syntax Tree)를 생성하고 바이트코드로 변환하여 실행한다.
이때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다.
변경된 DOM, CSSOM은 다시 렌더 트리로 결합된다.
렌더 트리를 기반으로 HTML 요소의 레이아웃(위치와 크기)을 계산하고 브라우저 화면에 HTML 요소를 페인팅 한다.
HTTP Pipelining
HTTP 1.0은 기본적으로 Connection 당 하나의 요청을 처리할 수 있습니다.
그렇기 때문에 동시전송은 불가능하고 하나의 요청에 대한 응답이 온 후 다음 요청을 처리하게 됩니다.
수 많은 멀티미디어 리소스들이 있는 상황에서 이러한 특징은 Network Latency를 발생시킵니다.
이를 위해 HTTP 1.1에서 HTTP Pipelining 이 도입되었습니다.
이는 TCP 안에 두 개 이상의 HTTP 요청을 담아 Network Latency을 줄이는 방식입니다.
하지만 이는 정확히 구현하기 힘들 뿐 아니라 HOL Blocking이 발생합니다.
HOL (Head of Line) Blocking
앞선 요청에 의해 뒤에 요청이 지연되는 것을 의미합니다.
HTTP Pipelining 을 통해 한 번에 여러 개의 이미지를 요청하는 경우를 생각해봅시다.
가장 앞에 요청한 이미지가 응답이 지연되면 두, 세번째 이미지도 지연이 발생합니다.
TCP 안에 여러 개의 HTTP 요청이 왔으므로 완료된 응답부터 보내면 되지 않을까라고 생각할 수 있지만 서버는 TCP에서 요청을 받은 순서대로 응답을 해야합니다.
무거운 Header
클라이언트와 서버 간에 수 많은 http 요청이 발생할 것이고 header의 정보는 대부분 동일합니다.
하지만 HTTP 1.1에서는 이러한 헤더를 중복해서 계속 보낼 뿐 아니라 cookie 정보 역시 매 요청마다 헤더에 포함되어 전송됩니다.
HTTP 2.0은 HTTP를 아예 새롭게 개선하기 보다는 기존 HTTP 1.1을 개선하는 방향에서, 성능 쪽에 초점을 맞춘 프로토콜로서2015년 2월 표준으로 승인되었습니다.
HTTP 1.1의 여러 문제점으로 구글이 개발한 비표준 개방형 프로토콜 SPDY를 기반하였습니다.
Multiplexed Streams
HTTP 1.1의 HTTP Pipelining 의 개선안으로 하나의 Connection으로 동시에 여러 개의 메세지를 주고 받을 수 있습니다.
응답은 요청 순서에 상관없이 Stream으로 받기 때문에 HOL Blocking 도 발생하지 않습니다.
Stream Prioritization
응답에 대한 우선순위를 정해 우선순위가 높을수록 응답을 빨리 합니다.
예를 들어 하나의 HTML 문서에 CSS 파일과 여러 IMG 파일이 있다고 가정해봅시다.
만일 여러 IMG 파일을 응답하느라 CSS 파일의 응답이 느려지면 클라이언트는 렌더링을 하지 못하고 기다리게 됩니다.
따라서 CSS 파일의 우선순위를 올려 렌더링을 진행하며 IMG 파일은 도착하는 대로 띄어준다면 더 효율적입니다.
Server Push
서버가 클라이언트의 요청없이 응답을 보내는 방법입니다.
위와 마찬가지로 하나의 HTML 문서에 CSS 파일과 여러 IMG 파일이 있다고 가정해봅시다.
기존에는 HTML 문서를 요청한 후 다시 각각의 CSS 파일과 여러 IMG 파일을 위한 요청을 보내야 했습니다.
하지만 Server Push 로 인해서 클라이언트의 요청을 최소화하고 서버가 리소스를 알아서 보내줍니다.
Header Compression
HTTP 1.1의 경우 이전 요청과 중복되는 Header도 똑같이 전송하느라 네트워크 자원을 불필요하게 낭비하였습니다.
HTTP 2.0의 경우, Header Table과 Huffman Encoding을 사용하는 HPACK 압축방식으로 이를 개선하였습니다.
클라이언트와 서버는 각각 Header Table을 관리하고 이전 요청과 동일한 필드는 table의 index만 보내고, 변경되는 값은 Huffman Encoding 후 보냄으로서 Header의 크기를 경령화 하였습니다.
서버에 존재하던 HTML 파일이 브라우저의 요청에 의해 응답된다.
브라우저는 서버가 응답한 HTML 문서를 바이트(2진수) 형태로 응답받는다. 응답된 바이트 형태의 HTML 문서는 meta태그의 charset 어트리뷰트에 의해 지정된 인코딩 방식을 기준으로 문자열로 변환한다.
문자열로 변환된 HTML 문서를 읽어들여 문법적 의미를 갖는 코드의 최소 다누이인 토큰들로 분해한다.
각 토큰들을 객체로 변환하여 노드들을 생성한다.
HTML 문서는 HTML 요소들의 집합으로 이루어지며 HTML 요소는 중첩 관계를 갖는다.
HTML의 요소의 콘텐츠 영역(시작 태그와 종료 태그 사이)에는 텍스트뿐만 아니라 다른 HTML 요소도 포함될 수 있다.
이때 HTML요소 간에는 중첩관계에 의해 부자 관계가 형성된다.
이러한 부자관계를 반영하여 모든 노드들을 트리 자료구조로 구성한다.
이 노드들로 구성된 트리 자료구조를 DOM(Document Object Model)이라 부른다.
렌더링 엔진은 HTML을 처음부터 한 줄씩 순차적으로 파싱하여 DOM을 생성해 나간다.
렌더링 엔진은 DOM을 생성해 나가다가 CSS를 로드하는 link 태그나 style 태그를 만나면 DOM 생성을 일시 중단한다.
link 태그의 href 어트리뷰트에 지정된 CSS 파일을 서버에 요청하여 로드한 CSS 파일이나 style 태그 내의 CSS를 HTML과 동일한 파싱과정을 거치며 해석하여 CSSOM(CSS Object Model)을 생성한다.
CSSOM은 CSS의 상속을 반영항 생성된다.
DOM고 CSSOM은 렌더링을 위해 렌더 트리로 결합된다.
렌터 트리는 렌더링을 위한 트리 구조의 자료구조다.
브라우저 화면에 렌더링되지 않는 노드와 CSS에 의해 비표시되는 노드들은 포함하지 않는다.
meta태그, script태그 등
display: none
즉 렌더 트리는 브라우저 화면에 렌더링되는 노드만으로 구성된다.
완성된 렌더 트리는 HTML 요소의 레이아웃(위치와 크기)을 계산하는 데 사용되며 브라우저 화면에 픽셀을 렌더링하는 페인팅(painting) 처리에 입력된다.
💨 이제까지의 과정은 반복해서 실행될 수 있다.
다음과 같은 경우 반복해서 레이아웃 계산과 페인팅이 재차 실행된다.
자바스크립트에 의한 노드 추가 또는 삭제
브라우저 창의 리사이징에 의한 뷰포트 크기 변경
HTML 요소의 레이아웃에 변경을 발생시키는 등의 스타일 변경
width / height, margin, padding, border, position, display, top/right/bottom/left ...
💨 레이아웃 계산과 페인팅을 다시 실행하는 리렌더링은 비용이 많이드는, 즉 성능에 악영향을 주는 작업이다.
렌더링 엔진은 HTML을 한 줄씩 순차적으로 파싱하며 DOM을 생성해 나가다가 자바스크립트 파일을 로드하는 script 태그나 자바스크립트 코드를 콘텐츠로 담은 script 태그를 만나면 DOM 생성을 일시 중단한다.
script 태그의 src 어트리뷰트에 정의된 자바스크립트 파일을 서버에 요청하여 로드한 자바스크립트 파일이나 script 태그 내의 자바스크립트 코드를 파싱하기 위해 자바스크립트 엔진에 제어권을 넘긴다.
자바스크립트 파싱과 실행은 브라우저의 렌더링 엔진이 아닌 자바스크립트 엔진이 처리한다.
자바스크립트 엔지은 자바스크립트 코드를 파싱하여 CPU가 이해할 수 있는 저수준 언어로 변환하고 실행하는 역할을 한다.
렌더링 엔진으로부터 제어권을 넘겨받은 자바스크립트 엔진은 자바스크립트 코드를 파싱하기 시작한다.
자바스크립트 엔진은 자바스크립트를 해석하여 AST(Abstract Syntax Tree: 추상적 구문 트리)를 생성한다.
AST를 기반으로 인터프리터가 실행할 수 있는 중간 코드인 바이트 코드를 생성하여 실행한다.
자바스크립트 코드에 DOM이나 CSSOM을 변경하는 DOM API가 사용된 경우 DOM이나 CSSOM이 변경된다.
이때 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합되고 변경된 렌더 트리를 기반으로 레이아웃과 페인트 과정을 거쳐 브라우저의 화면에 다시 렌더링 한다.
리플로우(reflow)
레이아웃 계산을 다시 하는 것
노드 추가/삭제, 요소의 크기/위치 변경, 윈도우 리사이징 등 레이아웃에 영향을 주는 변경이 발생한 경우에 한하여 실행
리페인트(repaint)
렌더링 엔진과 자바스크립트 엔진은 병렬적으로 파싱을 실행하지 않고 직렬적으로 파싱을 수행한다.
브라우저는 동기적으로, 즉 위에서 아래 방향으로 순차적으로 HTML, CSS, JavaScript를 파싱하고 실행한다.
이는 script 태그의 위치에 따라 HTML 파싱이 블로킹되어 DOM 생성이 지연될 수 있다는 것을 의미한다.
따라서 script 태그의 위치는 중요한 의미를 갖는다.
script 태그를 body 요소의 가장 아래에 위치시키는 것은 좋은 아이디어이다.
DOM이 완선됮 않은 상태에서 자바스크립트가 DOM을 조작하면 에러가 발생할 수 있다.
자바스크립트 로딩/파싱/실행으로 인해 HTML 요소들의 렌더링에 지장받는 일이 발생하지 않아 페이지 로딩 시간이 단축된다.
자바스크립트 파싱에 의한 DOM 생성이 중단되는 문제를 근본적으로 해결하기 위해 HTML5부터 script 태그에 async
와 defer
어트리뷰트가 추가되었다.
src 어트리뷰트를 통해 외부 자바스크립트 파일을 로드하는 경우에만 사용할 수 있다.
async
HTML 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행된다.
여러개의 script 태그에 async 어트리뷰트를 지정하면 script 태그의 순서와는 무관하게 로드가 완료된 자바스크립트부터 먼저 실행되므로 순서가 보장되지 않는다.
defer
HTML 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행된다.
DOMContentLoaded
이벤트 발생) 진행된다.DOM 생성이 완료된 이후 실행되어야 할 자바스크립트에 유용하다.