자바 스크립트의 런타임 환경인 Node.js의 등장으로 자바 스크립트는 웹 브라우저를 벗어나 서버 사이드 애플리케이션 개발에서도 사용할 수 있는 범용 개발언어
가 되었다. 하지만 아직도 자바 스크립트가 가장 많이 사용되는 분야는 클라이언트 사이드의 개발로 브라우저 환경을 고려할 때 더 효율적인 개발이 가능하다. 이를 위해 브라우저가 HTML, CSS, JavaScript로 작성된 파일을 어떻게 해석하여 브라우저를 렌더링하는지를 이해할 필요가 있다.
파싱(parsing)
: 구문 분석(syntax analysis)이라고도 불리는 파싱은 일련의 문자열을 의미있는 토큰으로 분해하고 이들로 이루어진 파스 트리(parse tree)를 만드는 과정
을 말한다. 일반적으로 파싱이 완료된 이후에는 파스 트리를 기본으로 한 중간 언어인 바이트 코드(bytecode)를 생성하고 실행한다.
바이트 코드(bytecode)
: 바이트코드(portable code, p-code)는 특정 하드웨어가 아닌 가상 컴퓨터에서 돌아가는 실행 프로그램을 위한 이진 표현법
이다.
토큰(token)
: 토큰은 어휘 분석(lexical analysis)의 단위
를 가리키는 컴퓨터 용어이다.
브라우저의 핵심 기능
은 필요한 리소스(html, css, JavaScript, image, etc)를 서버에 요청(Request)하고 응답(Response) 받아 브라우저에 시각적으로 렌더링
하는 것이다.
Q. 브라우저의 주소창에 URL을 입력하고 엔터 키를 누르면 어떤일이 발생할까?
URL과 IP 주소 사이의 관계
URL은 protocol, subdomain, domain name, TLD의 조합으로 구성되어 있다. 컴퓨터는 URL의 구성요소인 단어(words)를 이해할 수 없기 때문에 URL에는 고유한 IP 주소가 할당
된다. IP 주소는 .으로 구분된 0 ~ 255 사이 숫자 4개의 조합 형태(예시: 216.3.128.12)이다. 이 때문에 브라우저 주소창에 URL 대신 IP 주소를 직접 입력하여도 웹사이트에 접근할 수 있다. 이 IP 주소는 DNS(Domain Name System)에 저장되어 있으며 그로 인해 우리는 IP 주소를 기억하지 않고 도메인 이름으로 웹사이트에 손쉽게 접근
할 수 있게된다.
DNS 요청 전 단계
브라우저 주소창에 URL을 입력한 뒤 엔터 키를 누르면 브라우저는
첫번째로 브라우저 캐시에서 DNS 기록을 확인한다. 캐시는 한번이라도 방문했던 사이트의 IP 주소를 임시적으로 저장하는 공간으로 만약 캐시에서 도메인 네임의 IP 주소를 찾을 수 없다면 컴퓨터의 OS 호출 시스템을 만들어 OS 캐시, router 캐시를 순차적으로 확인
한다.
DNS 요청 단계
DNS 요청 전 단계 확인에 맞춰 확인을 했는데 DNS 기록을 찾을 수 없는 경우, 마지막으로 Internet Service Provider(ISP) 캐시를 확인한다. 브라우저는 인터넷을 통해 올바른 IP 주소를 탐색하는 과정을 거치게 되며 검색 결과로 올바른 IP를 수신하게 되면 프로토콜을 사용하여 HTTP 요청 메세지를 생성하고 이 메세지를 조회한 IP 주소의 컴퓨터에게 전송한다. 해당 컴퓨터에서 프로토콜의 요청을 해석하여 HTTP 응답 메세지를 생성하고 이 메세지를 요청을 보낸 컴퓨터에 전송
하게 된다. 도착한 응답 메세지는 HTTP 프로토콜을 사용하여 웹 페이지의 데이터로 변환되고 이는 브라우저를 통해 렌더링되어 브라우저에 입력한 URL 화면의 정보를 사용자가 볼 수 있게 된다. (가장 많이 사용되는 프로토콜은 TCP/IP : Transmission Control Protocol/Internet Protocol
이다.)
HTTP(HyperText Transfer Protocol)은 웹에서 브라우저와 서버가 통신하기 위한 프로토콜(규약)이다. HTTP 뒤에 붙는 숫자는 규약의 버전을 의미하며 1.1과 2.0의 큰 차이는 요청/응답의 처리 방식에 있다. 1.1(1999년 발표)은 하나의 요청은 하나의 응답만 처리 가능했으나 2.0(2015년 발표)은 다중 요청과 다중 응답이 가능하여 페이지 로드 속도가 이전에 비해 빨라졌다.
브라우저의 요청에 따라 서버가 응답한 HTML 문서는 문자열로 이루어진 순수한 텍스트이다. 이를 브라우저가 렌더링할 수 있는 자료구조(객체) 변환하기 위해서는 다음과 같은 과정을 거쳐 DOM(Document Object Model)을 생성한다. 바로 이 DOM이 HTML 문서를 파싱한 결과물이다.
렌더링 엔진은 서버로부터 응답된 HTML, CSS 파일을 파싱하여 각각 DOM, CSSOM을 생성
한다. 그리고 이 2개의 오브젝트 모델은 렌더링을 위해 렌더 트리로 결합된다. 이 과정에서 display: none;
이 적용된 비표시 요소는 렌더 트리 구성시 포함되지 않는다. 이러한 브라우저 렌더링 과정은 다음과 같은 상황에서 여러번 발생할 수 있다.
자바 스크립트로 인한 노드 추가 삭제로 발생하는 레이아웃 변경
브라우저 창의 리사이징으로 인한 Viewport 크기 변경으로 발생하는 레이아웃 변경
HTML 요소의 레이아웃 변경을 발생시키는 CSS 스타일(width, height, etc)의 변경
자바스크립트 파일은 렌더링 엔진이 아닌 자바스크립트 엔진이 처리
하며 각각의 파싱 과정에서의 주도권은 각자가 가진다. 그리고 각각의 엔진은 파싱을 직렬적으로 수행
하기 때문에 이런 주도권의 변경으로 인해서 서로간의 작업이 Block되는 상황이 발생하기도 한다. 따라서 HTML 파일 안의 <script> 태그의 위치가 매우 중요
하다.
DOM 요소 생성전에 자바 스크립트를 조작할 경우 참조 에러가 발생할 수 있다.
자바 스크립트 파싱으로 인해서 HTML 요소의 렌더가 지연되어 페이지 로딩시간이 증가할 수 있다.
38.8에서 발생할 수 있는 문제를 해결하기 위해 HTML5부터 script 태그에 async, defer라는 어트리뷰트가 추가되었다. 앞 2개의 어트리뷰트를 사용하면 HTML 파싱과 자바 스크립트 파일의 로드가 병렬적으로 실행된다는 공통점이 있지만 자바 스크립트 파일의 실행 시점이 다르다는 차이점도 있다.
async: 자바 스크립트 파일의 파싱을 HTML 파싱과 병렬적으로 수행하고 파일이 로드 완료된 시점에 바로 실행된다. HTML 파싱 중간에 로드가 완료되었다면 HTML 파일의 파싱이 중단될 수 있다.
defer: 자바 스크립트 파일의 파싱은 HTML 파싱과 병렬적으로 수행하지만 HTML 파싱이 완료되는 시점까지 기다렸다가 파일이 실행된다.
출처: 모던 자바스크립트 Deep Dive-이웅모