[JavaScript] 자바스크립트의 실행 컨텍스트(Execution Context) 완벽 정리 (ft. 스코프, TDZ, 호이스팅)
📌 실행 컨텍스트란?
실행 컨텍스트(Execution Context)는 자바스크립트 코드가 실행되는 환경을 정의하고 관리하는 개념이다. 여기에는 코드를 실행하는 데 필요한 변수, 함수 선언, this 바인딩 정보 등을 포함하며, 코드가 실행되는 동안 이를 추적하고 관리한다.
자바스크립트는 싱글 스레드 기반으로 동작하므로, 한 번에 하나의 코드 블록만 실행할 수 있다. 하지만 실행 컨텍스트를 콜 스택(Call Stack)으로 관리하면서 여러 코드 블록을 실행할 수 있도록 조율하게 된다.
📌 실행 컨텍스트의 종류
✅ 1) 전역 실행 컨텍스트 (Global Execution Context)
전역 실행 컨텍스트는 코드가 실행될 때 가장 먼저 생성되는 컨텍스트로, 프로그램이 종료될 때까지 유지된다.
전역에서 선언된 변수와 함수는 전역 실행 컨텍스트에 포함되며, 어디서든 접근 가능하다.
브라우저 환경에서는 window 객체, node.js 환경에서는 global 객체가 전역 실행 컨텍스트의 역할을 한다.
✅ 2) 함수 실행 컨텍스트 (Function Execution Context)
함수가 호출될 때마다 새롭게 생성되는 컨텍스트로, 함수 실행이 끝나면 해당 실행 컨텍스트는 콜스택에서 제거된다.
함수 내부의 변수 및 선언된 함수는 해당 컨텍스트 내에서만 접근 가능하다.
✅ 3) eval 실행 컨텍스트 (Eval Execution Context)
eval() 함수 내부에서 실행되는 코드에 대해서는 별도의 실행 컨텍스트가 생성된다.
eval은 보안 및 성능 상의 문제로 사용이 권장되지 않는다.
📌 실행 컨텍스트의 구성 요소
실행 컨텍스트는 다음 3가지 요소로 구성된다.
✅ 1) 환경 기록 (Environment Record)
현재 실행 컨텍스트 내에서 선언된 변수, 함수, this 등을 저장하는 공간이다.
전역 컨텍스트에서는 전역 객체(Global Object)가 환경 기록을 담당하며, 함수 컨텍스트에서는 활성 객체(Activation Object)가 그 역할을 한다.
👇 [참고] 전역 객체와 활성 객체
✨ 전역 객체
- 전역 실행 컨텍스트가 생성될 때 자동으로 생성되는 객체
- 전역 환경에서 선언된 변수, 함수, this 바인딩 정보를 저장하는 역할
- 브라우저 환경에서는 window 객체, node.js 환경에서는 global 객체가 전역 객체 역할을 함
- var로 선언한 전역 변수는 전역 객체의 속성으로 저장됨
- let과 const로 선언한 전역 변수는 전역 객체의 속성이 아님 (하지만 여전히 전역 환경 기록에는 존재함)
✨ 활성 객체
- 함수가 실행될 때 생성되는 실행 컨텍스트의 환경 기록을 관리하는 객체
- 함수 내부에서 선언된 변수, 함수, 함수 선언문 등을 저장하는 역할
- 실행 컨텍스트가 생성될 때, 활성 객체가 환경 기록을 담당하는 객체로 사용됨
- 함수가 실행될 때 생성되며, 함수 실행이 끝나면 사라짐
- var로 선언된 변수는 활성 객체에 저장되지만, let과 const는 활성 객체의 환경 기록에 저장됨 (TDZ 존재)
👇 [참고] var, let, const의 전역 객체 등록 차이: var는 전역 객체의 속성으로 등록되지만 let, const는 그렇지 않다는 게 무슨 뜻일까?
1️⃣ var는 전역 객체(window/global)의 속성으로 등록되지만, let과 const는 그렇지 않음
var a = "hello";
let b = "world";
const c = "javascript";
console.log(window.a); // "hello" (var는 전역 객체에 등록됨)
console.log(window.b); // undefined (let은 전역 객체에 등록되지 않음)
console.log(window.c); // undefined (const도 전역 객체에 등록되지 않음)
위의 코드를 보면 window.b와 window.c를 출력하면 undefined가 나온다. 변수 자체가 존재하지 않는다면, ReferenceError가 발생했을 것이다. 하지만 전역 실행 컨텍스트에 저장은 되어있지만, 전역 객체의 속성으로는 등록되어있지 않기 때문에 별도의 환경 기록(Environment Record)에 저장된다.
즉, var는 전역 객체(window)에 속성이 추가되지만, let과 const는 전역 실행 컨텍스트에는 존재하되 전역 객체의 속성이 아니다!
🧐 여기서 생기는 궁금증: 전역 실행 컨텍스트에 저장은 되어있지만 전역 객체의 속성이 아니라는 게 TDZ 때문인 건가?
👉 TDZ 때문이 아니라, 원래 자바스크립트의 스펙이 let, const는 전역 객체의 속성으로 추가되지 않도록 설계되었다. TDZ는 변수 선언 이전에 해당 변수에 접근하면 발생하는 문제이지, 전역 객체에 등록되지 않는 이유는 아님!
2️⃣ 함수 실행 시 var는 바로 활성 객체에 등록되며 undefined로 초기화됨 /
3️⃣ let과 const는 활성객체의 환경 기록으로 등록되지만, 선언문을 만나기 전까지 TDZ에 있음
function example() {
console.log(x); // undefined (var는 먼저 환경 기록에 등록되지만 undefined로 초기화됨)
console.log(y); // ReferenceError (let은 초기화되지 않음)
var x = 10;
let y = 20;
}
example();
var x는 함수 실행 시 활성 객체에 먼저 등록되고, 초기 값이 undefined로 설정된다. 반면 let y는 함수 실행 시 활성 객체에 등록되지만, 선언문을 만나기 전까지 접근이 불가하다. (TDZ)
즉, var는 함수 실행 시 바로 undefined로 초기화되지만, let과 const는 초기화되지 않고 TDZ 상태에 있다가 선언문을 만나야 사용할 수 있다!
✅ 2) 외부 환경 참조 (Outer Environment Reference)
실행 컨텍스트를 생성한 외부 렉시컬 환경을 참조하는 부분이다.
이를 통해 스코프 체인이 형성되며, 변수를 검색할 때 현재 컨텍스트에서 찾지 못하면 외부 컨텍스트로 탐색이 이어진다.
👇 [내가 보려고 남기는 참고] 실행 컨텍스트 - 스코프 체인 - 클로저
// 클로저 예시 코드
function outer() {
var secret = "나는 외부 함수의 변수야!";
return function inner() {
console.log(secret); // 🔥 외부 컨텍스트의 변수에 접근 가능
};
}
const closureFunc = outer(); // outer() 실행 후에도 secret이 살아 있음
closureFunc(); // "나는 외부 함수의 변수야!"
- 실행 컨텍스트가 생성되면, 스코프 체인을 따라 변수를 검색한다.
- 스코프 체인 덕분에 중첩 함수가 외부 함수의 변수에 접근 가능해진다.
- 실행이 끝나도 클로저를 통해 외부 함수의 변수를 계속 기억할 수 있다.
👉 즉, 실행 컨텍스트가 스코프 체인을 형성하고, 스코프 체인은 클로저가 동작하는 기반이 된다!
✅ 3) this 바인딩 (this Binding)
실행 컨텍스트가 어떤 객체를 참조하는지를 결정하는 요소이다.
전역 컨텍스트에서는 this가 window(브라우저 환경) 또는 global(node.js 환경)을 가리킨다.
함수 호출 방식에 따라 this의 값이 달라진다.
[참고] 자바스크립트의 this
[JavaScript] 자바스크립트의 this
📚 코어자바스크립트 (정재남, 2022) 책과 인프런의 코어자바스크립트 강의를 보고 배우거나 추가적으로 찾아본 것들을 정리한 내용입니다. 객체 지향 언어에서의 this 이번주 면접 스터디 발표
oliviakim.tistory.com
📌 실행 컨텍스트의 동작 과정
1. 전역 실행 컨텍스트 생성
가장 먼저 생성되며, 프로그램이 종료될 때까지 유지된다.
2. 함수 실행 컨텍스트 생성 (함수 호출 시)
함수가 호출될 때마다 새로운 실행 컨텍스트가 생성된다.
실행이 끝나면 해당 실행 컨텍스트는 제거된다.
3. 콜 스택(Call Stack)에서 실행 컨텍스트 관리
실행 컨텍스트는 콜 스택을 사용해 관리된다.
현재 실행 중인 컨텍스트는 콜 스택의 최상단(Top)에 위치한다.
실행이 끝난 컨텍스트는 콜 스택에서 제거된다.
📌 실행 컨텍스트와 호이스팅(Hoisting)
✅ 호이스팅이란?
실행 컨텍스트가 생성될 때 변수와 함수 선언을 먼저 메모리에 저장하는 과정이다. (주로 먼저 '끌어올려진다'라고 표현한다.)
코드 실행 전에 식별자 정보를 수집하여 환경 기록(Environment Record)에 등록한다.
var 키워드로 선언된 변수는 undefined로 초기화되며, 함수 선언문은 전체 함수가 저장된다.
✅ 호이스팅 예제
console.log(a); // undefined
var a = 10;
console.log(a); // 10
foo(); // 'Hello'
function foo() {
console.log('Hello');
}
var a는 환경 기록에 미리 등록되지만, 초기 값은 undefined로 설정된다.
따라서 console.log(a)를 실행하면 undefined가 출력된다
함수 선언문 foo()는 전체 함수가 호이스팅 되므로 정상 실행된다.
👇 [참고] 호이스팅과 TDZ
✨ 호이스팅 개념 다시 정리
- 자바스크립트에서 변수와 함수 선언이 실행 전에 끌어 올려지는(hoisting) 현상
- 즉, 코드 실행 전에 변수와 함수가 메모리에 미리 저장되는 과정
⭕ var, 함수 표현식(function(){ }): 호이스팅 될 때 초기화(undefined)까지 함께 수행
✖️ let, const, 함수 선언식(화살표 함수 () => { }): 선언만 호이스팅 되고, 초기화는 실행문을 만날 때 수행(TDZ)
✨ TDZ 개념 다시 정리
- let과 const는 선언 전 접근할 수 없는 TDZ(죽은 영역)를 가짐
- 즉, 변수 선언문을 만나기 전에 변수를 참조하면 ReferenceError가 발생!
- 결국 호이스팅 자체는 되지만, 초기화가 실행문을 만날 때 수행되기 때문에 접근할 수 없는 것
✨ 왜 TDZ가 발생할까?
- 스코프 내에서 안전한 변수 접근을 보장하기 위해서!
- var는 어디에서 선언되든 재할당, 재선언까지 가능한 전역 스코프이기 때문에 코드가 길어지고 복잡해질 수록 변수를 추적, 제어하기가 어려움
- 이러한 문제점을 해결하기 위해 나온 변수 선언 키워드가 let, const
- 따라서 변수가 '어디서 선언되었는지'를 명확히 하고, 의도치 않은 오류를 방지하기 위한 설계가 TDZ
📌 스코프와 스코프 체인
✅ 스코프(Scope)
변수의 유효 범위를 의미하며, 실행 컨텍스트가 변수를 참조할 수 있는 범위를 결정한다.
✅ 스코프 체인(Scope Chain)
실행 컨텍스트에서 변수를 검색할 때, 현재 컨텍스트에서 찾지 못하면 외부 컨텍스트를 참조하는 구조이다.
이를 통해 중첩 함수가 외부 함수의 변수에 접근할 수 있다.
✅ 스코프 체인 예제
var a = 1;
function outer() {
var b = 2;
function inner() {
var c = 3;
console.log(a, b, c); // 1, 2, 3
}
inner();
}
outer();
inner() 함수 내부에서 a, b, c를 출력할 때,
- c는 inner()에서 찾는다.
- b는 outer()에서 찾는다.
- a는 전역 컨텍스트에서 찾는다.
이렇게 변수를 가까운 스코프에서부터 차례로 찾는 과정이 스코프 체인이다.
📢 실행 컨텍스트 최종 정리
- 자바스크립트 코드가 실행될 때 전역 실행 컨텍스트가 생성된다.
- 함수가 호출될 때마다 새로운 실행 컨텍스트가 생성된다.
- 실행 컨텍스트는 환경 기록, 외부 환경 참조, this 바인딩으로 구성된다.
- 호이스팅을 통해 변수와 함수 선언이 먼저 메모리에 저장된다.
- 콜스택을 사용해 실행 컨텍스트를 관리하며, 실행이 끝난 컨텍스트는 스택에서 제거된다.