브라우저, DNS

브라우저

사용자가 연결된 주소의 서버에 데이터 요청을 하게 되면 서버로부터 데이터를 다운받은 것을 가지고 웹브라우저가 그것을 해석해서 사용자가 보는 UI를 완성 해줍니다. 따라서 네트워크가 중요합니다.

브라우저의 기본 구조

  1. 사용자 인터페이스 주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등 요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분입니다.
  2. 브라우저 엔진 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어합니다.
  3. 렌더링 엔진 요청한 콘텐츠를 표시. 예를 들어 HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시합니다.
  4. 통신 HTTP요청과 같은 네트워크 호출에 사용됩니다.
  5. UI 백엔드 콤보 박스와 창 같은 기본적인 장치를 그립니다. 플랫폼에서 명시하지 않은 일반적인 인터페이스로서, OS 사용자 인터페이스 체계를 사용합니다.
  6. 자바스크립트 해석기 말 그대로 자바스크립트 코드를 해석하고 실행합니다.
  7. 자료 저장소 자료를 저장하는 계층입니다.

파싱 이란??

파싱이란 브라우저가 코드를 이해하고 사용할 수 있는 구조로 변환하는 것을 의미합니다. 파싱 결과는 보통 문서 구조를 나타내는 노드 트리인데 파싱 트리 또는 문법 트리라고 부릅니다.

파싱과 script 태그

자바스크립트인 script태그를 만나면 스크립트가 해석 및 실행되는 동안 문서의 파싱은 중단됩니다. 스크립트가 외부에 있는 경우 우선 네트워크로부터 자원을 가져와야 하는데 이 또한 실시간으로 처리되고 자원을 받을 때까지 파싱은 중단됩니다. 스타일 시트는 이론적으로 DOM트리를 변경하지 않기 때문에 문서 파싱을 기다리거나 중단하지 않습니다. 따라서 스크립트 파일을 먼저 로드하고 스크립트가 문서를 파싱하는 동안 스타일 정보를 요청하는 경우라면 문제가 생깁니다. 스크립트가 문서를 파싱하는동안 브라우저는 다른 작업을 수행하지 않기 때문에 스타일이 파싱되지 않은 상태가 되고 이렇게 된다면 레이아웃이 제대로 구성되지 않은 상태로 사용자에게 뷰를 제공할 확률이 높아져 사용자 경험을 떨어뜨리는 결과를 초래할 수 있습니다. 이러한 문제가 있기에 스크립트 소스를 body태그 끝에 두는 것을 권장합니다.

스크립트 로드 async, defer

body태그의 마지막에 스크립트 태그를 삽입하는 방식이 오래된 브라우저에서도 잘 작동하게 합니다. 다만 head 영역에 스크립트가 삽입되거나 외부의 파일에 정의되어 있다면 이벤트 연결은 문서의 로드시점에 맞게 처리해야 하는데 이 경우에 모던 브라우저에서는 defer,async속성을 사용할 수 있습니다.

  • async 속성이 추가된 경우 async 속성이 명시된 경우 브라우저가 페이지를 파싱하는 동안 스크립트가 실행됩니다. 즉 스크립트 파일이 비동기적으로 실행됩니다. HTML 구문 해석기가 스크립트 태그에 도달한 지점에서 스크립트를 실행하기 위해 파싱을 중단할 필요가 없습니다. 따라서 HTML 구문 분석과 병행해서 스크립트를 가져온 후 스크립트가 준비가 될 때마다 즉시 실행이 가능해지는데, 실행의 순서가 다운로드 완료 시점에 의해 결졍되므로 실행 순서가 중요한 스크립트들에 async를 사용할떄는 유의해야 합니다.
  • defer 속성이 추가된 경우의 실행 async 속성은 명시되어 있지 않고 defer 속성만 명시된 경우라면 브라우저가 페이지의 파싱을 모두 끝내면 스크립트가 실행됩니다. 비동기적으로 로드된 스크립트와 마찬가지로 HTML 구문 분석이 실행되는 동안 HTML해석을 방해하지 않고 스크립트 파일을 다운로드 할 수 있습니다. async속성과의 차이점은 HTML 구문 분석이 완료되기 전에 스크립트 다운로드가 완료 되더라도 구문 해석이 완료될 때까지 스크립트는 실행되지 않습니다.

렌더링 과정

  1. 브라우저는 HTML, stylesheets를 해석합니다. 그리고 HTML 문서의 해석이 끝나면 DOM 트리를 만들고 stylesheets 문서의 해석이 끝나면 스타일 규칙 CSSOM Tree을 만듭니다.
  2. 만든 DOM 트리와 스타일 규칙을 합쳐 렌더 트리를 만듭니다. 여기에서 flow(배치)과정이 실행되어 각 요소들의 레이아웃을 위치시킵니다.
  3. Layout 단계로 와서 브라우저의 뷰포트 내에서 각 노드들의 정확한 위치와 크기를 계산합니다. 생성된 렌더트리 노드들이 가지고 있는 스타일과 속성에 따라서 브라우저 화면의 어느 위치에 어느 크기로 출력될지 게산하는 단계입니다. 뷰포트 크기가 달라질 경우 매번 계산을 다시합니다.
  4. Layout 게산이 완료되면 Paint과정으로 요소들을 실제 화면에 그립니다. 이전 단계에서 이미 요소들의 위치, 크기, 스타일 계싼이 완료된 렌더트리를 이용해 실제 픽셀 값을 채워넣습니다. 이 과정에서 텍스트, 색, 이미지, 그림자 효과등이 처리되어 그려지는데, 처리해야 하는 스타일이 복잡할수록 Paint 단계에 소요되는 시간이 늘어납니다.

Reflow

렌더링 과정을 거친 뒤 최종적으로 페이지가 그려진다고 해서 끝나는것이 아닙니다. 특정 액션이나 이벤트에 따라 html요소의 크기나 위치, 레이아웃 등을 수정하면 그에 영향을 받는 자식이나 부모들을 포함하여 위에서 설명한 Layout 과정을 다시 수행합니다. 그러면 렌더트리와 각 요소들의 크기 및 위치를 다시 계산하는데 이 과정을 Reflow라고 합니다.

Reflow가 일어나는 예시

  • 페이지 초기 렌더링 시
  • 윈도우 리사이징 시 (뷰포트 크기 변화)
  • 노드 추가 또는 제거
  • 요소의 위치, 크기 변경(top, left, margin, padding, border, width 등)
  • 폰트 변경과 이미지 크기 변경
const reflowEvent = () => {
  document.getElementById('box').stlye.width = '500px'
}

Repaint

처음 렌더링 과정에서 알 수 있듯이, flow Layout과정이 수행된다고해서, 실제로 화면에 반영되지 않습니다. 변경된 내용대로 렌더트리를 다시 화면에 그려주는 과정이 필요한데, 결국 paint 단계가 다시 수행되는 것이고 이를 Repain라고 합니다.
Reflow가 일어나야만 Repain가 일어나는것은 아닙니다. 백그라운드 색상이나, 오퍼시티와 같이 레이아웃에는 영향을 주지 않는 스타일 속성이 변경되었을 때는 Reflow는 수행할 필요가 없기에 Repain만 실행합니다. 즉 리플로우는 너비, 높이 레이아웃상 위치가 변경되었을 때 발생하고, 리페인트만 발생할때는 레이아웃에는 영향을 주지않고, 요소의 가시성과 관련된 오퍼시티, 백그라운드 등이 변경될때 발생합니다.

const repaintEvent = () => {
  document.getElementById('box').stlye.backgroundColor = 'red'
}

위의 reflow와 repaint는 수정된 렌더 트리를 다시 렌더링 하는 과정에서 발생하며 웹 어플리케이션의 성능을 떨어뜨리는 주된 요인입니다. 극단적으로 CSS효과로 인해 자바스크립트의 실행 속도가 느려질 수 있습니다.

reflow, repaint 줄이기

  1. 클래스 변경을 통해 스타일을 변경할 경우에는 최대한 말단 노드의 클래스를 변경한다. 말단에 있는 노드를 변경함으로써, 리플로우의 영향을 최소화 시킵니다.
  2. 인라인 스타일을 사용하지 않는다. 스타일 속성을 통해 스타일을 설정하면, 리플로우가 발생합니다. 인라인 스타일은 HTML이 다운로드될 때 레이아웃에 영향을 미치면서 추가 리플로우를 발생시킵니다.
  3. 애니메이션이 들어간 요소는 position: fixed, position: absolute로 지정한다. absolute 또는 fixed 위치인 요소는 다른 요소의 레이아웃에 영향을 미치지 않습니다. 리플로우가 아닌 리페인트가 발생하기에 상대적으로 적은 비용이 듭니다.
  4. 부드러운 애니메이션이 성능을 저하시킨다. 예를들어 한번에 1px씩 요소가 이동하면 보이는것은 부드럽게 보이지만, 성능이 떨어지는 디바이스는 성능 이슈가 생길 수 있습니다. 요소가 4px씩 이동한다면 부드러운것은 덜하겠지만, 리플로우 처리가 1/4만 필요합니다.
  5. 레이아웃을 위한 table태그는 피한다. table태그는 점진적으로 렌더링되는것이 아니라, 모두 불려지고 계산된 다음에 렌더링이 됩니다. 또한 작은 변경만으로도 테이블의 다른 모든 노드에 대한 리플로우가 발생합니다.
  6. css 하위 셀렉터를 최소화한다. 사용하는 규칙이 적을수록 리플로우가 빠릅니다.
  7. 숨겨진 요소를 변경한다. display: none으로 숨겨진 요소는 변경될 때, 리페인트나 리플로우가 발생하지 않습니다.
  8. 자바스크립트를 통해 스타일을 변경할 경우, .cssText를 사용하거나, 클래스를 변경한다. 한 요소의 너비, 높이, 여백을 변경하려고 하면
const element = document.getElementById('box')

el.style.padding = '20px'
el.style.width = '200px'
el.style.height = '150px'

위 처럼 변경할 경우에는 3번의 리플로욱라 발생하게 됩니다.

const element = document.getElementById('box')

el.style.cssText = 'padding: 20px; width: 200px; height: 150px;'

위처럼 cssText를 사용하거나, 다른 클래스를 적용하면 3번이 발생하던 리플로우가 한번만 발생하게 됩니다.

DNS

DNS(Domain Name System)은 호스트의 도메인이름(즉 www.google.com)을 네트워크 주소(123.123.1.0)로 변환하거나 반대의 역할을 수행하는 시스템 입니다.
DNS 시스템은 마치 별명이나 전화번호부 같은 기능을 하며 DNS 서버는 사용자가 도메인 이름을 브라우저에 입력하면, 사용자를 어떤 서버에 연결할 것인지 제어합니다.
모든 도메인 이름은 트리구조로 구성되어 있습니다.
.(root)로 부터 1단계 최상위 도메인(.com, .net, .kr)로 부터 2단계(.co.kr, .go.kr, naver.com) 3단계(abc.co.kr) 의 형태를 지니고 있습니다.

DNS는 어떻게 동작할까?

  1. 사용자가 브라우저에 www.google.com을 입력하면 PC는 미리 설정되어 있는 DNS(Local DNS)에게 www.google.com이라는 hostname에 대한 IP주소를 요청합니다.
  2. Local DNS 에 www.google.com의 IP 주소가 있는지 없는지 확인을 한 뒤, www.google.com의 주소가 있다면 IP 주소를 줍니다.
  3. Local DNS는 www.google.com 의 주소를 찾기 위해 다른 DNS 서버들과 통신을 합니다. Root DNS 서버에 www.google.com의 IP 주소를 요청하며 이를 위해 각 Local DNS 서버에는 Root DNS 서버의 주소가 미리 설정되어 있어야 합니다.
  4. Root DNS 서버가 www.google.com의 주소를 찾을 수 없다면 찾을 수 없으니 다른 DNS 서버에 물어보라는 응답을 합니다.
  5. Local DNS 서버는 .com 도메인을 관리하는 DNS 서버에 www.google.com의 주소를 요청합니다. .com 서버는 google도메인을 관리하는 서버 정보를 알려줍니다.
  6. .com 도메인 서버에도 정보가 없다면 google.com 도메인을 관리하는 DNS 서버에 요청합니다.
  7. google.com 도메인 서버는 www.google.com의 주소가 있으니 응답을 해줍니다.

Written by@jaeyoung-son
배운것을 기록하는 공간입니다.

GitHub