이 주제를 한 문장으로 설명하면?
"JS 엔진이 소스코드를 평가하고 실행하기 위해 필요한 환경을 관리하는 객체,그리고 호이스팅 / 스코프 체인 / this 바인딩 / 클로저가모두 이 안에서 구현되는 방식"
이 주제가 왜 중요한가?
개념 설명:
JS 엔진은 소스코드를 두 단계로 처리한다.
① 소스코드 평가 (Creation Phase) 실행 컨텍스트를 생성하고, 변수/함수 등의 선언문만 먼저 실행해 생성된 식별자를 렉시컬 환경의 환경 레코드에 등록한다.
② 소스코드 실행 (Execution Phase) 평가가 끝나면 선언문을 제외한 소스코드가 순차적으로 실행된다(런타임 시작). 실행에 필요한 변수/함수 참조를 환경 레코드에서 검색해 취득하고, 실행 결과를 다시 환경 레코드에 등록한다.
실제 코드 예시:
var x; // 평가 단계: x가 환경 레코드에 등록, undefined 초기화x = 1; // 실행 단계: x에 1 할당
[평가 단계]환경 레코드: { x: undefined } ← 선언문만 먼저 실행[실행 단계]x = 1 실행 → 환경 레코드: { x: 1 }
핵심 포인트:
개념 설명:
실행 컨텍스트는 LexicalEnvironment 컴포넌트와 VariableEnvironment 컴포넌트로 구성된다. 생성 초기에는 둘 다 동일한 렉시컬 환경을 참조한다.
렉시컬 환경은 두 개의 컴포넌트로 구성된다.
실행 컨텍스트├─ LexicalEnvironment│ ├─ 환경 레코드 (Environment Record)│ │ → 식별자 등록 및 값 관리 (호이스팅 원리)│ └─ 외부 렉시컬 환경에 대한 참조 (Outer Lexical Environment Reference)│ → 상위 스코프 참조 (스코프 체인 원리)└─ VariableEnvironment→ var 선언 관리 (초기엔 LexicalEnvironment와 동일)
콜스택(실행 컨텍스트 스택):
var x = 1;const y = 2;function foo(a) {var x = 3;const y = 4;function bar
[콜스택 변화]1. 전역 코드 평가 → 전역 실행 컨텍스트 생성 → push스택: [ 전역 EC ]2. foo(10) 호출 → foo 실행 컨텍스트 생성 → push스택: [ 전역 EC, foo EC ]3. bar(20) 호출 → bar 실행 컨텍스트 생성 → push스택: [ 전역 EC, foo EC, bar EC ] ← 현재 실행 중인 컨텍스트4. bar 종료 → pop스택: [ 전역 EC, foo EC ]5. foo 종료 → pop스택: [ 전역 EC ]6. 전역 코드 종료 → pop스택: []
핵심 포인트:
개념 설명:
실행 컨텍스트는 전역과 함수 두 종류가 있는데, 환경 레코드 구조가 다르다.
전역 실행 컨텍스트의 환경 레코드:
전역 환경 레코드는 객체 환경 레코드와 선언적 환경 레코드 두 가지로 구성된다.
전역 환경 레코드├─ 객체 환경 레코드 (Object Environment Record)│ → var 선언 전역 변수, 함수 선언문, 빌트인 전역 프로퍼티/함수 등록│ → BindingObject를 통해 전역 객체(window)의 프로퍼티로도 연결됨│ → 그래서 var x = 1 → window.x === 1└─ 선언적 환경 레코드 (Declarative Environment Record)→ let, const 선언 전역 변수 등록→ 전역 객체와 연결되지 않음 → window.y로 참조 불가
객체 환경 레코드가 존재하는 이유는 ES6 이전부터 var와 전역 객체 프로퍼티가 동일하게 동작해야 했기 때문이다. var x = 1과 window.x = 1이 같은 것처럼 동작하는 이유가 바로 이것이다.
함수 실행 컨텍스트의 환경 레코드:
함수 환경 레코드는 선언적 환경 레코드만 존재한다.
함수 환경 레코드└─ 선언적 환경 레코드만 존재→ 함수 내부의 var, let, const, 매개변수 등 모두 여기에 등록→ 함수 내부 변수가 전역 객체 프로퍼티가 될 이유가 없으니객체 환경 레코드가 필요 없음
실제 코드 예시:
// 전역var x = 1; // 객체 환경 레코드 → window.x === 1let y = 2; // 선언적 환경 레코드 → window.y === undefinedconsole.log(window.x); // 1console.log(window
핵심 포인트:
개념 설명:
환경 레코드는 스코프에 포함된 식별자를 등록하고, 등록된 식별자에 바인딩된 값을 관리하는 저장소다. 평가 단계에서 선언문이 먼저 실행되어 식별자가 환경 레코드에 등록되는 것이 호이스팅의 실체다.
실제 코드 예시:
console.log(x); // undefined (var: 등록 + undefined 초기화)console.log(y); // ❌ ReferenceError: TDZ (let: 등록만, 초기화 안 됨)console.log(foo); // function foo() {} (함수 선언문: 함수 객체 통째로 등록)var x = 1;
[평가 단계] 환경 레코드 상태:객체 환경 레코드:x: undefined ← var, undefined 초기화foo: function foo(){} ← 함수 선언문, 함수 객체 전체 등록선언적 환경 레코드:y: <uninitialized> ← let, TDZ (초기화 안 됨)
핵심 포인트:
개념 설명:
외부 렉시컬 환경에 대한 참조는 상위 스코프를 가리킨다. 이 참조를 통해 단방향 링크드 리스트인 스코프 체인을 구현한다.
중요한 점은 상위 스코프가 함수를 어디서 호출했는지가 아니라 어디에 정의했는지에 따라 결정된다는 것이다. 이것이 렉시컬 스코프(정적 스코프)다.
실제 코드 예시:
var x = 1;const y = 2;function foo(a) {var x = 3;const y = 4;function bar(
스코프 체인 구조:
bar 실행 컨텍스트렉시컬 환경:환경 레코드: { b: 20, z: 5 }외부 참조: → foo의 렉시컬 환경│▼foo 실행 컨텍스트렉시컬 환경:환경 레코드: { a: 10, x: 3, y: 4, bar: fn }외부 참조: → 전역 렉시컬 환경│▼전역 실행 컨텍스트렉시컬 환경:환경 레코드: { x: 1, y: 2, foo: fn }외부 참조: → null (스코프 체인 종점)
bar에서 x를 찾는 식별자 결정 과정:
bar 환경 레코드에서 x 탐색 → 없음↓ 외부 참조 따라 이동foo 환경 레코드에서 x 탐색 → 있음! (x: 3) → 3 반환(전역의 x: 1은 탐색하지 않음 — foo의 x가 먼저 발견됨)
핵심 포인트:
개념 설명:
실행 컨텍스트 생성 시 this 바인딩이 결정된다. 함수가 어떻게 호출되는지에 따라 this에 바인딩될 값이 동적으로 결정된다.
전역 코드 → this: 전역 객체 (window / global)일반 함수 호출 → this: 전역 객체 (strict mode에서는 undefined)메서드 호출 → this: 메서드를 호출한 객체생성자 함수 호출 → this: 새로 생성된 인스턴스화살표 함수 → this 바인딩 없음 → 상위 스코프 this 사용
실제 코드 예시:
const obj = {name: 'foo',greet() {console.log(this.name); // 메서드 호출 → this = obj},greetArrow: ()
핵심 포인트:
개념 설명:
함수는 자신이 정의된 환경에 의해 결정된 상위 스코프의 참조를 함수 객체 내부 슬롯 [[Environment]]에 저장한다. 외부 함수가 종료되어 실행 컨텍스트가 스택에서 제거되어도, 내부 함수의 [[Environment]]가 외부 함수의 렉시컬 환경을 참조하고 있으면 GC에 수거되지 않고 메모리에 유지된다.
실제 코드 예시:
const x = 1;function outer() {const x = 10;const inner = function () {console.log(x)
클로저 메모리 유지 구조:
innerFunc[[Environment]] → outer의 렉시컬 환경│▼outer의 렉시컬 환경환경 레코드: { x: 10, inner: fn }(outer EC는 스택에서 제거됐지만innerFunc의 [[Environment]]가 참조 중 → GC 수거 안 됨)
핵심 포인트:
"실행 컨텍스트가 뭔가요?" 질문에 대한 답변 스크립트
실행 컨텍스트는 JS 엔진이 소스코드를 평가하고 실행하기 위해 필요한환경을 관리하는 객체입니다.코드는 평가와 실행 두 단계로 처리되는데요,평가 단계에서 실행 컨텍스트가 생성되고 변수와 함수 선언문이 먼저 실행되어렉시컬 환경의 환경 레코드에 등록됩니다. 이게 호이스팅의 실체입니다.렉시컬 환경은 환경 레코드와 외부 렉시컬 환경에 대한 참조로 구성되는데,외부 참조가 상위 스코프를 단방향으로 연결해 스코프 체인을 구현합니다.클로저는 함수가 정의될 때 상위 스코프의 렉시컬 환경을[[Environment]]에 저장하기 때문에, 외부 함수가 종료된 후에도참조 중인 렉시컬 환경이 메모리에 유지되어 외부 변수에 접근할 수 있는 것입니다.
⏱️ 1분 요약 버전
실행 컨텍스트는 코드 실행 환경을 관리하는 객체로,평가 단계에서 식별자를 환경 레코드에 등록하고(호이스팅),외부 렉시컬 환경 참조로 스코프 체인을 구현합니다.함수는 정의 시점의 상위 스코프를 [[Environment]]에 저장하기 때문에렉시컬 스코프가 구현되고, 클로저도 가능합니다.
A: JS 엔진이 소스코드를 평가하고 실행하기 위해 필요한 환경을 관리하는 객체입니다. 함수가 호출될 때마다 생성되어 실행 컨텍스트 스택에 push되고, 종료되면 pop됩니다. 렉시컬 환경을 통해 식별자를 관리하고, 스코프 체인과 호이스팅, this 바인딩이 모두 여기서 구현됩니다.
A: 소스코드 평가 단계에서 실행 컨텍스트가 생성될 때, 변수와 함수 선언문이 먼저 실행되어 식별자가 환경 레코드에 등록되기 때문입니다. var는 등록과 동시에 undefined로 초기화되고, let/const는 등록만 되고 초기화가 안 되어 선언문에 도달하기 전까지 TDZ가 발생합니다. 함수 선언문은 함수 객체 자체가 등록되어 선언 전에도 호출 가능합니다.
A: 변수를 찾을 때 현재 실행 컨텍스트의 환경 레코드를 먼저 탐색하고, 없으면 외부 렉시컬 환경에 대한 참조를 따라 상위 스코프로 이동하며 탐색합니다. 전역 렉시컬 환경까지 올라가도 없으면 ReferenceError가 발생합니다. 외부 참조가 단방향 링크드 리스트로 연결된 것이 스코프 체인의 실체입니다.
A: 됩니다. 평가 단계에서 환경 레코드에 등록되는 건 동일합니다. 다만 var와 달리 초기화가 선언문에 도달할 때 이루어지기 때문에, 그 전까지 TDZ(Temporal Dead Zone) 구간이 생겨 참조하면 ReferenceError가 발생합니다. 호이스팅이 안 된다는 표현은 정확하지 않고, TDZ 때문에 에러가 나는 것입니다.
A: 아닙니다. 외부 렉시컬 환경 참조는 함수가 선언된 시점에 함수 객체의 [[Environment]]에 저장된 렉시컬 환경을 따릅니다. 함수는 어디서 호출되든 상관없이 정의된 위치의 상위 스코프가[[Environment]]에 고정되어 있기 때문에, 호출 시점의 실행 컨텍스트와는 무관하게 항상 정의 시점의 렉시컬 환경이 Outer로 설정됩니다.
오늘 이 주제에서 가장 중요한 것 3가지