브라우저의 동작원리와 자바스크립트 함수, Event, Ajax ,Timer 같은 Web API들이 어떤 순서로 처리되는지 알아보자.

브라우저 동작 원리

브라우저는 크게 렌더링 엔진, 자바스크립트 엔진으로 나뉜다.

브라우저는 사용자가 참조하고자 하는 페이지를 서버에게 요청 후 그에 대한 응답을 화면에 보여주는 일을 한다.

  • 렌더링 엔진
    서버로부터 받은 HTML, CSS은 브라우저 렌더링 엔진의 HTML 파서, CSS 파서에 의해 DOM, CSSOM 트리가 만들어지고 렌더 트리로 결합된다. 렌더 트리를 기반으로 브라우저는 웹페이지를 표시한다.
  • 자바스크립트 엔진
    자바스크립트 처리는 자바스크립트 엔진이 하며 JS로 작성한 코드를 해석하고 실행하는 인터프리터다. 렌더링 엔진의 HTML 파서가 DOM 생성 프로세스를 하던 중 스크립트 태그를 만나면, 자바스크립트 코드를 실행시키기 위해 자바스크립트 엔진에게 제어권한을 넘겨 주게 된다.
    DOM 트리가 다 형성되지 않았는데 자바스크립트에서 해당 DOM을 조작하려고 하면 문제가 발생하기 때문에 <script> 태그는 html의 body 태그 제일 아래에 놓는 것이 좋다.

    외부의 자바스크립트 파일을 불러오는 경우는?

    외부 자바스크립트 파일의 겨우 브라우저가 일시 중지하고 디스크, 캐시 또는 원격 서버에서 스크립트를 가져올 때까지 기다려야 한다. 이로 인해 주요 렌더링 경로에 수십~ 수천 밀리초의 지연이 추가로 발생할 수 있다.

    자바스크립트 엔진

    자바스크립트는 싱글 스레드 언어로 한번에 하나의 태스크만 처리할 수 있다. 구글에서 개발한 V8를 비롯해 대부분의 자바스크립트 엔진은 크게 세 영역으로 나뉜다.
  • Call Stack
  • Task Queue(Evnet Queue)
  • Heap

1. Call Stack

호출 스택은 자바스크립트 프로그램에서 우리가 어디에 있는지 기록하는 데이터 구조이다. 함수를 실행하면 함수에 대한 기록을 스택 제일 위에 추가한다(push). 함수의 결과값을 반환하면 스택에서 제거된다. (pop!)

function foo(b){
    var a = 5;
      return a * b + 10 
}
function bar(x) {
var y = 3;
  return foo(x*y);
}

console.log(bar(6));
  1. 먼저 bar라는 함수를 호출했으니 bar에 해당하는 스택 프레임이 형성 되고, 그 안에는 y와 같은 local variable과 argument가 함께 생성된다.
  2. bar 함수는 foo 함수를 호출하고 있다. 아직 bar는 값을 반환하지 않은 상태이니 스택에서 pop 되지 않고 호출된 foo 함수가 Call Stack에 push 된다.
  3. foo 함수에서는 a* b + 10라는 값을 리턴하면서 함수의 역할을 마쳤으므로 stack에서 pop 된다.
  4. 다시 bar 함수로 돌아와서 foo 함수로 받은 값을 리턴하며 함수를 종료하고 스택에서 pop 된다.

자바스크립트는 단일 호출 스택을 사용한다. 이 말은 하나의 함수가 실행되고 있으면 이 함수의 실행이 끝날때까지 다른 태스크들은 수행될 수 없다는 뜻이다. 앞의 예 처럼 호출된 함수가 차례대로 쌓이고 값을 반환하면서 순서대로 pop 된다.

2. Heap

메모리 힙은 동적으로 만들어진 객체(인스턴스)가 메모리에 할당되는 곳이다.

이러한 단일 호출 스택의 문제점은 스택이 한번에 하나의 일만 처리할 수 있기 때문에 만약 그 함수가 복잡한 연산을 수행해야 한다고 하면 다른 함수가 실행되지 못하는 상황이 생긴다.

가장 쉬운 해결책은 비동기 콜백 을 사용하는 것이다. 우리 코드의 일부를 실행하고 나중에 실행될 콜백함수를 제공한다. 비동기 콜백은 즉시 호출 스택에 쌓이지 않고 Event Queue에서 기다렸다가 호출 스택이 비어있는 시점에 실행된다.

3. Event Queue

  • 자바스크립트의 런타임 환경의 이벤트 큐는 처리할 메세지 목록과 실행할 콜백 함수들의 리스트이다. 버튼 클릭 같은 이벤트나 DOM 이벤트, http 요청, setTimeout 같은 비동기 함수는 Web API를 호출하며 Web API는 콜백 함수를 콜백 큐에 밀어 넣는다.
  • 이벤트 큐는 대기하다가 스택이 텅!!!! 비는 시점에 이벤트 루프를 돌려 해당 콜백 함수를 스택에 넣는다. 이벤트 루프의 기본 역할은 큐와 스택 두 부분을 지켜보고 있다가 스택이 비는 시점에 콜백을 실행시켜 주는 것이다.
  • 웹 브라우저에서는 이벤트가 발생할 때마다 메세지가 추가되고 이벤트 리스너가 첨부된다. 콜백 함수의 호출은 호출 스택의 초기 프레임으로 사용되며 자바스크립트가 싱글 스레드 이므로 스택에 대한 모든 호출이 반환될 때까지 메세지 폴링 및 처리가 중지 된다. 동기식 함수 호출은 이와 반대로 새 호출 프레임을 스택에 추가한다.

이벤트 큐와 이벤트 루프의 동작을 잘 보여주는 setTimeout 예

   setTimeout(function() {
     console.log("first");
   }, 0);
    console.log("second");

// console >>
// second
// first
  • setTimeout에 0ms를 주었으니 바로 실행되는 것이 아니다. setTimeout은 호출 스택에서 실행된 후 Web API의 Timer API를 호출한다. Web API에 의해 setTimeout의 콜백함수는 이벤트 큐에 enqueue 된다. 만약 addEventlitenser(
    'click', clickHandler) 라면 클릭되는 순간 Web API가 이벤트 큐에 clickHandler를 추가시킨다.

  • console.log('second') 가 호출 스택에 쌓이고 second가 실행된 후 호출 스택이 비었을 때 first가 콘솔창에 나타나게 되는것이다.

    setTimeout의 delay를 0으로 주더라도 0초 보다 더 걸리는 이유는 콜 스택에서 모든 프레임이 실행될 때까지 기다려야하기 때문이다.

    elem.addEventlistener('click', () => foo(value))

    elem = document.querySelector('...');
    elem.addEventlistener('click', foo()); //(1)
    elem.addEventlistener('click', foo); // (2)
    elem.addEventlistener('click', () => foo(value)); //(3) 

    (1) 이렇게 핸들러를 바로 호출해버리면 이벤트 발생까지 기다리지 않고 바로 실행 됨
    (2) click 될 때 event queue로 넘어가기 위해서 핸들러 이름만 적어줌, 잉 ? 근데 인자를 전달못함
    (3) 편-안..이벤트 발생 때까지 기다렸다가 실행될 수 있고 인자를 전달 할 수 있음

    참고 자료