JavaScript의 Execution Context란 무엇이고 왜 중요한가

Frontend

JavaScript 기본기를 공부하다 보면 개별 개념은 어느 정도 이해했는데도, 머릿속에서 서로 연결이 안 되는 순간이 자주 옵니다.

  • scope는 선언 위치 기준이라 하고
  • hoisting은 실행 전에 준비된다고 하고
  • closure는 바깥 스코프를 기억한다고 하고
  • this는 호출 방식에 따라 달라진다고 하고
  • 함수 호출은 call stack에 쌓인다고 합니다

각각 따로 보면 이해한 것 같지만, 막상 "왜 이런 규칙들이 한 언어 안에서 같이 성립하지?"라고 물으면 흐릿해지기 쉽습니다.

이때 중심축이 되는 개념이 execution context입니다.

겉보기에는 "코드가 실행되는 환경" 정도로 설명되곤 합니다. 물론 틀린 말은 아닙니다. 하지만 기본기 관점에서는 그것만으로 부족합니다. 중요한 것은 execution context지금 어떤 코드가 실행 중인지, 어떤 식별자를 볼 수 있는지, this는 무엇인지, 어떤 함수가 다음에 실행될지를 한꺼번에 묶는 실행 단위라는 점입니다.

이 글에서는 JavaScriptexecution context를 아래 흐름으로 정리해보겠습니다.

  1. execution context는 무엇인지
  2. 왜 중요한지
  3. global context, function context는 무엇이 다른지
  4. creation phaseexecution phase는 무엇인지
  5. scope, hoisting, this, call stack과 어떻게 연결되는지

한눈에 보면

먼저 짧게 정리하면 이렇습니다.

  • execution context는 현재 코드 실행에 필요한 정보를 묶어 관리하는 실행 단위입니다.
  • 자바스크립트는 코드 실행 시 전역 컨텍스트와 함수 컨텍스트를 만듭니다.
  • 각 컨텍스트는 보통 "준비 단계"와 "실행 단계"를 거칩니다.
  • hoisting, scope, this는 모두 execution context 안에서 설명되는 개념입니다.
  • 함수 호출이 일어날 때마다 새 컨텍스트가 생기고, 이것들이 call stack 위에 쌓입니다.

즉, execution context는 별도 부가 개념이 아니라, 자바스크립트가 코드를 실제로 실행하는 방식의 중심 모델에 가깝습니다.

execution context는 정확히 무엇일까?

가장 단순하게 말하면 execution context현재 실행 중인 코드가 동작하는 데 필요한 정보들을 담는 실행 문맥입니다.

예를 들어 자바스크립트 엔진이 어떤 함수를 실행하려고 할 때, 단순히 "이 코드 줄을 읽자"로 끝나지 않습니다. 적어도 아래 같은 정보가 필요합니다.

  • 지금 어떤 코드 블록을 실행 중인지
  • 어떤 식별자를 볼 수 있는지
  • this는 무엇인지
  • 매개변수와 지역 변수는 무엇인지
  • 다음에 어떤 함수가 실행될 수 있는지

즉, 실행 컨텍스트는 단순한 추상 이론이 아니라 엔진이 코드를 실행하기 위해 필요한 현재 상태 묶음이라고 보는 편이 맞습니다.

execution context가 중요할까?

이 개념이 중요한 이유는, 자바스크립트 기본기의 여러 개념이 사실 여기서 한 번에 연결되기 때문입니다.

예를 들어:

  • var는 선언 전에 undefined가 보이는가
  • let, constTDZ에 걸리는가
  • 왜 함수는 바깥 스코프를 볼 수 있는가
  • this는 호출 방식에 따라 달라지는가
  • 왜 함수 호출이 끝나면 이전 코드로 되돌아가는가

이 질문들은 사실 전부 "실행 컨텍스트가 어떻게 만들어지고 쌓이고 제거되는가"와 연결됩니다.

즉, execution context를 이해하면 개별 문법 지식이 아니라 실행 모델 전체가 더 선명해집니다.

자바스크립트는 어떤 컨텍스트를 만들까?

기본적으로 크게 보면 아래 두 가지를 먼저 떠올리면 좋습니다.

1. Global Execution Context

프로그램이 시작될 때 가장 먼저 생기는 전역 실행 컨텍스트입니다.

즉:

  • 파일 최상단 코드
  • 전역 변수
  • 전역 함수

같은 것들이 처음 평가되는 문맥입니다.

보통 자바스크립트 프로그램이 시작되면 전역 실행 컨텍스트가 먼저 만들어지고, 모든 함수 호출은 그 위에서 시작됩니다.

2. Function Execution Context

함수가 호출될 때마다 새로 만들어지는 실행 컨텍스트입니다.

예를 들어:

function add(a: number, b: number) {
  return a + b;
}
 
add(1, 2);

이때 add(1, 2)가 실행되면 add를 위한 함수 실행 컨텍스트가 만들어집니다.

즉:

  • 매개변수 a, b
  • 함수 내부 변수
  • 함수 내부의 this

같은 것들이 이 컨텍스트 안에서 관리됩니다.

함수가 호출될 때마다 컨텍스트가 새로 생긴다는 말은 무슨 뜻일까?

이 부분이 중요합니다.

같은 함수라도 호출될 때마다 별도 실행 컨텍스트가 만들어집니다.

function printNumber(num: number) {
  console.log(num);
}
 
printNumber(1);
printNumber(2);

여기서 printNumber는 같은 함수이지만:

  • 첫 번째 호출은 num = 1인 컨텍스트
  • 두 번째 호출은 num = 2인 컨텍스트

를 각각 가집니다.

즉, 함수 정의는 하나여도 실행 문맥은 호출마다 별개입니다.

이 감각이 있어야:

  • 재귀 함수
  • 클로저
  • 콜백 중첩

도 덜 헷갈립니다.

creation phaseexecution phase는 무엇일까?

실행 컨텍스트를 설명할 때 가장 자주 나오는 구분입니다.

1. Creation Phase

실제로 코드를 한 줄씩 실행하기 전에, 이 컨텍스트에서 필요한 것들을 먼저 준비하는 단계입니다.

대체로 이 시점에:

  • 식별자 이름이 등록되고
  • 함수 선언문이 먼저 준비되고
  • var, let, const가 각각 다른 규칙으로 처리되고
  • this 값이 결정될 수 있습니다

즉, 이 단계는 "실행 준비 단계"에 가깝습니다.

2. Execution Phase

그다음 실제 코드가 위에서 아래로 실행되는 단계입니다.

이 시점에:

  • 변수에 값이 대입되고
  • 표현식이 평가되고
  • 함수가 호출되고
  • 조건문과 반복문이 실행됩니다

즉, 실행 컨텍스트는 단순히 한 번 생겨서 끝나는 것이 아니라, 준비 단계와 실제 실행 단계를 함께 갖는다고 보는 편이 좋습니다.

hoisting이 여기서 설명될까?

hoisting은 사실 execution context의 준비 단계와 아주 깊게 연결됩니다.

예를 들어:

console.log(value);
var value = 10;

이 코드에서 undefined가 보이는 이유는, 실행 컨텍스트의 준비 단계에서:

  • value라는 이름이 먼저 등록되고
  • var 규칙상 undefined로 초기화되기 때문입니다

반면:

console.log(value);
let value = 10;

는 준비 단계에서 이름이 등록되더라도, 초기화 전 접근이 막히기 때문에 TDZ 에러가 납니다.

즉, hoisting은 "코드가 위로 이동한다"보다 컨텍스트 생성 단계에서 선언이 어떻게 준비되는가로 설명하는 편이 더 정확합니다.

scope도 여기서 연결될까?

실행 컨텍스트는 단지 "지금 함수가 도는 중"이라는 정보만 갖는 것이 아니라, 지금 어떤 식별자를 볼 수 있는가도 같이 관리합니다.

예를 들어:

const globalValue = 'global';
 
function outer() {
  const outerValue = 'outer';
 
  function inner() {
    const innerValue = 'inner';
    console.log(globalValue, outerValue, innerValue);
  }
 
  inner();
}

inner()의 실행 컨텍스트는:

  • 자기 지역 식별자
  • 바깥 함수 스코프
  • 전역 스코프

와 연결된 이름 탐색 구조를 가집니다.

즉, scope chain은 추상 규칙이라기보다 현재 실행 컨텍스트가 어떤 외부 환경을 참조하는가와 연결됩니다.

이 말은 곧, scope도 컨텍스트 밖의 독립 개념이 아니라 컨텍스트 안에서 식별자를 해석하는 방식과 연결된다는 뜻입니다.

closure도 여기서 설명될까?

closure는 함수가 바깥 스코프를 계속 볼 수 있는 현상입니다.

예를 들어:

function createCounter() {
  let count = 0;
 
  return function increment() {
    count += 1;
    return count;
  };
}

이때 increment()가 나중에 실행될 수 있는 이유는, 이 함수가 자신이 선언될 때 연결된 바깥 환경을 계속 참조할 수 있기 때문입니다.

즉, 클로저는 뜬금없는 별도 기능이 아니라 함수 실행 컨텍스트와 외부 렉시컬 환경 연결이 유지되는 결과로 이해하는 편이 더 정확합니다.

this도 execution context와 연결될까?

this 역시 함수가 실행될 때 결정되는 중요한 정보입니다.

예를 들어:

const user = {
  name: 'Marco',
  printName() {
    console.log(this.name);
  },
};
 
user.printName();

여기서 printName() 실행 시점에는 단순히 코드 본문만 필요한 것이 아니라:

  • 이 함수의 this가 무엇인지

도 같이 필요합니다.

즉, this는 함수 밖에 따로 떠 있는 값이 아니라, 실행 컨텍스트가 들고 있어야 하는 현재 실행 정보의 일부라고 보는 편이 자연스럽습니다.

이 감각이 서 있으면:

  • 왜 메서드 분리 시 this가 깨지는지
  • bind, call, apply가 필요한지
  • arrow function은 다르게 보이는지

도 훨씬 잘 연결됩니다.

call stack은 execution context와 어떤 관계가 있을까?

이 부분도 매우 중요합니다.

함수가 호출될 때마다 새로운 execution context가 만들어지고, 이 컨텍스트들은 call stack에 쌓입니다.

예를 들어:

function first() {
  second();
}
 
function second() {
  third();
}
 
function third() {
  console.log('done');
}
 
first();

흐름을 감각적으로 보면:

Global Context
  -> first Context
    -> second Context
      -> third Context

처럼 쌓입니다.

그리고 third()가 끝나면:

  • third 컨텍스트가 스택에서 빠지고
  • second로 돌아가고
  • 그다음 first
  • 그다음 전역

으로 되돌아갑니다.

즉, call stack은 함수 이름 목록이라기보다 실행 컨텍스트가 쌓이는 구조라고 보는 편이 더 정확합니다.

왜 자바스크립트 실행 흐름을 이해할 때 call stack이 중요할까?

이 감각이 없으면:

  • 왜 함수 실행이 끝나면 이전 코드로 돌아가는지
  • 왜 재귀가 깊어지면 stack overflow가 생기는지
  • 왜 동기 함수 호출이 순서대로 막히는지

같은 질문이 전부 분리돼 보입니다.

즉, call stack은 단순 자료구조 설명이 아니라 컨텍스트가 어떻게 생기고 사라지는가를 보여주는 핵심 도구입니다.

전역 코드와 함수 코드는 왜 체감이 다를까?

전역 코드는 프로그램 시작 시 한 번 만들어진 전역 컨텍스트 안에서 평가됩니다.

반면 함수 코드는:

  • 호출될 때마다 새로운 컨텍스트를 만들고
  • 자기 매개변수와 지역 상태를 가지며
  • 끝나면 스택에서 빠집니다

즉, 함수 코드는 전역 코드보다 훨씬 동적인 문맥을 가집니다.

이 차이를 이해하면:

  • 왜 전역 변수는 오래 남는지
  • 왜 함수 안 변수는 호출마다 새로 생기는지
  • 왜 클로저가 특별하게 느껴지는지

도 훨씬 설명하기 쉬워집니다.

실무에서는 어디서 특히 중요할까?

1. hoisting 설명이 흐릿할 때

실행 전에 무엇이 준비되는지 감각이 없으면 var, let, const 차이가 전부 암기처럼 느껴집니다.

2. 함수 호출 흐름 디버깅

중첩 호출, 콜백, 재귀를 볼 때 어떤 컨텍스트가 현재 스택 위에 있는지 이해하면 흐름을 훨씬 잘 읽을 수 있습니다.

3. thisscope를 섞어 생각할 때

둘은 다른 규칙으로 움직이지만, 둘 다 execution context와 연결되기 때문에 차이를 구조적으로 설명하기 좋습니다.

4. 클로저와 상태 유지

어떤 값이 왜 계속 살아 있는지 설명하려면 결국 실행 문맥과 외부 환경 연결을 같이 봐야 합니다.

자주 하는 실수

정리하면 아래 실수가 정말 자주 나옵니다.

  • execution context를 막연히 "실행 환경" 정도로만 외운다
  • hoisting, scope, this를 서로 분리된 개념처럼만 본다
  • 함수 호출마다 새 컨텍스트가 생긴다는 점을 놓친다
  • call stack을 단순 함수 목록 정도로 이해한다
  • 컨텍스트의 준비 단계와 실행 단계를 구분하지 못한다

즉, execution context는 어려운 개념이라기보다 개별 기본기를 하나로 묶는 프레임으로 이해하는 편이 훨씬 안정적입니다.

실무 체크리스트

실제로 코드를 읽을 때는 아래 질문으로 빠르게 점검하면 도움이 됩니다.

  1. 지금 실행 중인 코드는 전역 코드인가, 함수 코드인가?
  2. 이 함수가 호출되면서 새 컨텍스트가 생겼는가?
  3. 이 컨텍스트에서 어떤 식별자가 준비돼 있고, 어떤 값은 아직 초기화되지 않았는가?
  4. 지금 this는 무엇으로 결정됐는가?
  5. 현재 스택 위에 어떤 컨텍스트들이 쌓여 있는가?

이 기준으로 보면 개별 문법이 아니라, 코드가 실제로 어떤 순서와 규칙으로 움직이는지가 훨씬 잘 보입니다.

같이 보면 좋은 글

결론

JavaScriptexecution context는 단순히 "코드가 실행되는 환경"이 아니라, 현재 실행 중인 코드가 어떤 식별자를 보고, 어떤 this를 가지며, 어떤 순서로 호출되고 돌아가는지를 묶어 설명하는 실행 단위입니다.

짧게 정리하면:

  • 전역 컨텍스트와 함수 컨텍스트가 있고
  • 각 컨텍스트는 준비 단계와 실행 단계를 거치며
  • scope, hoisting, closure, this는 모두 이 안에서 설명되고
  • 함수 호출이 일어날 때마다 컨텍스트가 call stack에 쌓입니다

결국 execution context를 이해하면 자바스크립트 기본기 개념들이 따로 놀지 않고, 하나의 실행 모델 안에서 왜 그렇게 동작하는지를 훨씬 더 자연스럽게 설명할 수 있게 됩니다.