Javascript는 도대체 어떻게 동작할까?

huurray·2021년 2월 11일
1
post-thumbnail

JavaScript를 다루기 위해서 기본적인 문법과 API 사용법에 대해서 익히는 것도 중요하지만, 엔진단에서의 자바스크립트 동작 원리도 알아야 한다고 생각한다.

복잡한 Javacript 엔진의 정확한 이해는 아직 어렵지만 몇 가지 핵심적인 내용만 간단히 정리하고자한다.

Call Stack

콜스택을 이해하려면 자바스크립트의 실행 컨텍스트, 즉 함수들이 어떻게 실행되는가의 기본원리를 설명해야합니다.

자바스크립트를 검색해보면 "싱글 쓰레드"기반이라고 이야기한다.

간단하게 이야기해서 작업장이 하나이고 그 때문에 한 번에 한 작업만 할 수 있고, 나머지 호출된 작업(call)을 자료구조형의 스택(stack) 방식으로 일을 처리한다는 것이다.

좁은 통에 공을 넣으면 가장 마지막에 넣은 공을 먼저 빼야하듯이 스택에 쌓인 콜 역시 가장 마지막에 들어온 콜이 가장 먼저 실행된다. 다시 말해, 실행된 함수 끝나면 또 스택의 가장 위쪽의 함수를 꺼내는 것이고 스택이 없을때까지 반복하는 것이다.

function multiply(a, b) {
  return a * b;
}
function square(n) {
  return multiply(n, n);
}
function printSquare(n) {
  var squared = square(n);
  console.log(squared);
}
printSquare(4);

이 코드가 실행되면 printSquare 함수가 실행되고 순차적으로 square, multiply 함수가 실행되고 마지막으로 콘솔이 찍히게 된다.

처리과정은 다음과 같다.

또, 호출 스택이 최대 크기가 되면 에러를 발생하는데 스택을 날려버린다고 표현한다. 이는 반복문 코드를 잘못 작성해서 무한 루프에 빠졌을 때 자주 발생하곤 한다.

function foo() {
  foo();
}

foo();

엔진에서 이 코드를 실행할 때, foo()에 의해서 foo함수가 호출된다. 그런데 여기서 foo함수가 반복적으로 자신을 다시 호출하는 재귀 호출을 수행하고, 그러면 매번 실행할 때마다 호출 스택에 foo()가 쌓이게 된다.

그림으로 보면 아래와 같다.

이렇게 메모리 힙에 할당 할 수 있는 최대한의 콜스택이 쌓였을 때 브라우져에서는 다음과 같은 에러를 내며 멈춘다.

그럼 자바스크립트는 어떻게 동시성(Concurrency)을 지원할까?

스레드가 하나라는 말은 곧, 동시에 하나의 작업만을 처리할 수 있다라는 말이다.
하지만 실제로 자바스크립트가 사용되는 환경을 생각해보면 많은 작업이 동시에 처리되고 있는 걸 볼 수 있다.

그건 자바스크립트가 Non-Blocking 프로세스를 가지고 있기 때문이다.

Non-Blocking

Non-Blocking은 I/O 방식 중 하나인데, I/O는 컴퓨터가 데이터를 주고받을 때의 입출력 프로세스를 말한다.

앞서 이야기한 Javascript의 Event Loop는 처리가 끝나기 전에 다음 콜을 처리하기 때문에 오래걸리는 작업을 효율적으로 처리할 수 있다. (싱글쓰레드이지만 마치 멀티쓰레드처럼 동작한다.)

이러한 방식이 Non-Blocking이고, Blocking은 그 반대의 방식이다.

그럼 비동기 처리(Asynchronous Callback)는?

console.log("Hi");

setTimeout(function () {
  console.log("there");
}, 5000);

console.log("JSconf");

이 코드는 먼저 'Hi'라는 콘솔이 찍히고, setTimeout이 실행되고 'JSconf'라는 콘솔이 찍히고 5초뒤에 'there'이라는 콘솔이 찍힌다.

그러면 setTimeout 함수가 아직 끝나지 않았는데 어떻게 'JSconf'라는 콘솔이 찍혔을까?

자바스크립트가 비동기 처리를 할 수 있는 것은 Event Loop 개념이 있고, 브라우저가 단순 런타임 이상의 작업을 하기 때문이다. (node js는 V8엔진 이겠지?)

자바스크립트는 한번에 하나의 일 밖에 할 수 없지만, 브라우저가 다양한 Web API들을 제공한다. (네트워크 호출, 타이머 등)

이들은 자바스크립트의 함수를 호출 할 수 있는 쓰레드를 제공한다.

setTimeout이 실행되면 브라우저로 넘어가 타이머를 실행시키고 카운트다운을 시작한다.
그래서 setTimeout의 호출 자체는 완료되었고, 스택에서는 제거 한다.

그리고 카운트다운이 끝나면 해당 작업이 갈데가 없으니 CallBack Queue라는 큐를 하나를 만들어 저장한다.

그리고 콜스택의 실행해야하는 작업이 없다면 콜백 큐에서 콜스택으로 보내서 작업을 처리한다.

이런 과정의 반복이 Event Loop 개념이다.

다시 정리하면 Call Stack과 Callback Queue의 상태를 주시하다가,
Call Stack이 빈 상태가 되면 Callback Queue의 첫번째 콜백을 Call Stack으로 쌓고 그걸 반복해서 처리하는 것이다. 이러한 반복적인 행동은 틱(tick)이라 한다.

다시 위의 코드를 보면 이제야 'JSconf'라는 콘솔이 먼저 실행되고 카운트 다운이 끝나면 'there'이 찍히는 것이 이해가 된다.

마지막으로 다음 코드는 이러한 엔진의 특성을 이용해 콜의 순서를 조절하는 workaround이다.

console.log("start");

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

console.log("end");

마무리

Javascript가 어떻게 동작하는가? 라고 물어볼 때 항상 궁금했던 것들에 대해 좀 이해가 되는 느낌이다.

이 언어의 몇가지 특성을 명확하게 알고 짜는 코드와 그렇지 않은 코드가 다를거라고 생각한다.

참고

2014 JSconf 강의 중 일부

profile
Frontend Developer.

0개의 댓글