
// * 콜 스택 동작 예시
function greet(name) {
return `안녕, ${name}`
}
function main() {
const result = greet('현우')
console.log(result)
}
main()
──────────────────────────────────
실행 흐름:
[ 전역 EC (Execution Context) 생성 ]
main() 호출
┌─────────────────────────────────────────┐
│ main EC (main 함수의 Execution Context) │ ← 콜 스택에 push
├─────────────────────────────────────────┤
│ 전역 EC (Global Execution Context) │
└─────────────────────────────────────────┘
greet() 호출
┌──────────────────────────────────────────┐
│ greet EC (greet 함수의 Execution Context) │ ← 콜 스택에 push
├──────────────────────────────────────────┤
│ main EC (main 함수의 Execution Context) │
├──────────────────────────────────────────┤
│ 전역 EC (Global Execution Context) │
└──────────────────────────────────────────┘
greet 반환
┌─────────────────┐
│ main EC │ ← greet EC가 pop됨
├─────────────────┤
│ 전역 EC │
└─────────────────┘
main 반환
┌─────────────────┐
│ 전역 EC │ ← main EC가 pop됨
└─────────────────┘실행 컨텍스트 내부
ExecutionContext {
LexicalEnvironment ← 식별자(변수, 함수) 바인딩 + 외부 스코프 참조
VariableEnvironment ← var 선언 전용 (초기에는 LexicalEnvironment와 동일)
ThisBinding ← this가 무엇인지
}LexicalEnvironment {
EnvironmentRecord ← 이 컨텍스트에서 선언된 식별자들의 실제 저장소
outer ← 바깥 LexicalEnvironment를 가리키는 참조
}스코프 체인
const x = 1 // 전역
function outer() {
const y = 2
function inner() {
const z = 3
console.log(x, y, z) // 1, 2, 3
}
inner()
}
──────────────────────────────
inner EC
EnvironmentRecord: { z: 3 }
outer: ──────────────────────→ outer EC
EnvironmentRecord: { y: 2, inner: fn }
outer: ─────────────────────────────→ 전역 EC
EnvironmentRecord: { x: 1, outer: fn }
outer: null생성 단계 (Creation Phase)
─────────────────────────
1. EnvironmentRecord에 식별자를 등록한다
- var → undefined로 초기화
- let/const → 등록만 하고 초기화하지 않음 (TDZ 상태)
- 함수 선언문 → 함수 객체 전체를 등록
2. outer 참조를 설정한다 (스코프 체인 형성)
3. this 바인딩을 결정한다
실행 단계 (Execution Phase)
──────────────────────────
코드를 위에서 아래로 순서대로 실행한다
변수에 실제 값을 대입한다console.log(name) // undefined — 에러가 아님
var name = '현우'
console.log(name) // '현우'생성 단계:
EnvironmentRecord: { name: undefined } ← var는 undefined로 초기화됨
실행 단계:
console.log(name) → EnvironmentRecord에서 name을 찾음 → undefined 반환
name = '현우' → EnvironmentRecord의 name에 '현우' 할당
console.log(name) → '현우' 반환greet('현우') // '안녕, 현우' — 선언 전에 호출해도 동작함
function greet(name) {
return `안녕, ${name}`
}생성 단계:
EnvironmentRecord: { greet: function greet(name) { ... } }
← 함수 선언문은 함수 객체 전체가 등록됨
실행 단계:
greet('현우') → EnvironmentRecord에서 greet 찾음 → 함수 객체 반환 → 호출console.log(name) // ReferenceError: Cannot access 'name' before initialization
let name = '현우'생성 단계:
EnvironmentRecord: { name: <uninitialized> }
← let/const는 등록은 되지만 초기화되지 않은 상태
실행 단계:
console.log(name)
→ EnvironmentRecord에서 name을 찾음
→ 존재하지만 uninitialized 상태
→ ReferenceError 발생
let name = '현우'
→ 이 시점에서 name이 '현우'로 초기화됨var / let / const hoisting 비교
생성 단계 실행 단계(선언 전) 실행 단계(선언 후)
var undefined undefined 할당값
let uninitialized ReferenceError 할당값
const uninitialized ReferenceError 할당값 (재할당 불가)
함수선언 함수 객체 전체 함수 객체 전체 함수 객체 전체
함수표현식 undefined undefined 함수 객체// 함수 선언문 — 어디서든 호출 가능
greet() // ✅ 동작
function greet() {
console.log('hello')
}
// 함수 표현식 — 할당 전에 호출 불가
greet() // ❌ TypeError: greet is not a function
var greet = function() {
console.log('hello')
}function makeCounter() {
let count = 0 // ← makeCounter의 LexicalEnvironment에 존재
return function increment() {
count++
return count
}
}
const counter = makeCounter()
counter() // 1
counter() // 2
counter() // 3makeCounter() 호출 시점
makeCounter EC
LexicalEnvironment:
EnvironmentRecord: { count: 0, increment: fn }
outer: → 전역 EC
increment 함수 객체 생성
[[Environment]]: → makeCounter의 LexicalEnvironment ← 이 참조가 저장됨makeCounter EC가 콜 스택에서 제거된 후
counter (increment 함수 객체)
[[Environment]]: ─────────→ makeCounter의 LexicalEnvironment (메모리에 유지)
EnvironmentRecord: { count: 0 }
outer: → 전역 EC
counter() 호출 시
increment EC
EnvironmentRecord: {} ← increment 자체 변수 없음
outer: ─────────────────────→ makeCounter의 LexicalEnvironment ← [[Environment]]에서 설정됨
EnvironmentRecord: { count: 0 }
count++ → outer를 타고 count를 찾음 → count: 0 → 1로 증가function makeCounter() {
let count = 0
return {
increment() { count++ }, // ← 같은 LexicalEnvironment 참조
decrement() { count-- }, // ← 같은 LexicalEnvironment 참조
getCount() { return count }, // ← 같은 LexicalEnvironment 참조
}
}
const counter = makeCounter()
counter.increment()
counter.increment()
counter.decrement()
counter.getCount() // 1 — 세 함수가 count를 공유함for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i) // 3, 3, 3 — 0, 1, 2가 아님
}, 100)
}전역 EnvironmentRecord: { i: 3 } ← 루프 끝나면 3
setTimeout 콜백 1 [[Environment]] → 전역 EC (i를 찾으면 3)
setTimeout 콜백 2 [[Environment]] → 전역 EC (i를 찾으면 3)
setTimeout 콜백 3 [[Environment]] → 전역 EC (i를 찾으면 3)for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i) // 0, 1, 2
}, 100)
}반복 1: LexicalEnvironment { i: 0 } ← 콜백 1의 [[Environment]]
반복 2: LexicalEnvironment { i: 1 } ← 콜백 2의 [[Environment]]
반복 3: LexicalEnvironment { i: 2 } ← 콜백 3의 [[Environment]]const x = 'global'
function outer() {
const y = 'outer'
function inner() {
const z = 'inner'
console.log(x, y, z)
}
inner()
}
outer()── 생성 단계 ────────────────────────────────────────────────
전역 EC 생성
EnvironmentRecord: { x: undefined, outer: fn } ← var 방식과 유사하게, const도 TDZ
outer: null
this: window (브라우저) / global (Node.js)
── 실행 단계 ────────────────────────────────────────────────
x = 'global' → 전역 EnvironmentRecord: { x: 'global', outer: fn }
outer() 호출
outer EC 생성
EnvironmentRecord: { y: undefined, inner: fn } ← 생성 단계
outer: → 전역 EC의 LexicalEnvironment
y = 'outer' → outer EnvironmentRecord: { y: 'outer', inner: fn }
inner() 호출
inner EC 생성
EnvironmentRecord: { z: undefined } ← 생성 단계
outer: → outer EC의 LexicalEnvironment ← inner 함수의 [[Environment]]
z = 'inner'
console.log(x, y, z)
z → inner EnvironmentRecord에서 발견 → 'inner'
y → 없음 → outer 타고 이동 → outer EnvironmentRecord에서 발견 → 'outer'
x → 없음 → outer 타고 이동 → outer EnvironmentRecord에서 없음 → 전역으로 이동 → 'global'
inner EC 종료 → 콜 스택에서 pop
outer EC 종료 → 콜 스택에서 pop
전역 EC 종료개념 | 어느 단계에서 발생하는가 | 핵심 메커니즘 |
호이스팅 | 생성 단계 | EnvironmentRecord에 식별자가 먼저 등록됨 |
스코프 체인 | 생성 단계 | outer 참조가 설정됨 ([[Environment]] 기반) |
클로저 | 함수 객체 생성 시 | [[Environment]]에 LexicalEnvironment 참조가 저장됨 |
TDZ | 생성 단계 ~ 실행 단계 | let/const가 uninitialized 상태로 등록됨 |
