스코프와 호이스팅 — 변수의 작동 원리
변수가 어디서 살아있는지·언제 보이기 시작하는지·클로저의 첫 인상.
3편에서 살짝 본 스코프·호이스팅·TDZ 를 12편에서 정확히 다집니다. 그리고 21편에서 다룰 클로저 의 첫 인상까지. 이 셋을 이해하면 "왜 이 변수가 안 보이지" "왜 undefined 가 나오지" 같은 입문 사고를 거의 다 막을 수 있습니다.
스코프 — 변수가 "보이는" 범위
// 1) 글로벌 스코프 — 어디서나 보임
const APP = "junai";
function f1() {
// 2) 함수 스코프 — f1 안에서만
const local = "x";
console.log(APP); // OK — 글로벌 보임
console.log(local); // OK
}
console.log(local); // ❌ 함수 밖에선 안 보임
if (true) {
// 3) 블록 스코프 — { } 안에서만 (let/const 만)
const blockOnly = "y";
}
console.log(blockOnly); // ❌
| 글로벌 | 함수 | 블록 | |
|---|---|---|---|
| 선언 위치 | 최상위 | function() 안 | {} 안 |
| let/const | 전역 | 함수만 | 블록만 |
| var | 전역 | 함수만 | 블록 무시 ⚠️ |
스코프 체인 — 안에서 밖으로
const a = "global";
function outer() {
const b = "outer";
function inner() {
const c = "inner";
console.log(a, b, c); // "global" "outer" "inner"
}
inner();
}
변수를 찾을 때 안 → 밖 순서로 차례로 봅니다. 같은 이름이 있으면 가장 가까운 게 이깁니다(shadowing). 이게 다음에 나올 클로저의 기반.
호이스팅 — "선언이 끌어올려진 것처럼"
// 함수 선언문 — 완전히 호이스팅
greet(); // OK
function greet() { console.log("hi"); }
// var — 선언만 호이스팅, 값은 undefined 로 시작
console.log(x); // undefined ← 에러 X
var x = 5;
// let/const — 선언은 호이스팅되지만 사용 금지 구역 (TDZ)
console.log(y); // ❌ ReferenceError: Cannot access 'y' before initialization
let y = 5;
// 함수 표현식·화살표 — 변수 호이스팅 규칙 따름
sayHi(); // ❌ Cannot access 'sayHi' before initialization
const sayHi = () => console.log("hi");
왜 호이스팅이 있나. JS 엔진이 코드 실행 전에 "어떤 변수·함수가 있나" 를 먼저 스캔합니다 — 그 결과 함수와 var 가 미리 알려진 상태로 위에 올라온 것처럼 보입니다. 의도된 안전장치가 아니라 옛 설계 결정 + 호환성 부산물에 가깝습니다.
TDZ (Temporal Dead Zone)
{
// 여기서부터 TDZ 시작
// (let/const 선언은 호이스팅됐지만 사용 금지)
console.log(z); // ❌ ReferenceError
let z = 10;
// 여기서부터 TDZ 끝
console.log(z); // 10
}
TDZ 는 짜증나 보이지만 선언 전 사용을 막는 안전장치입니다. 옛 var 의 "undefined 로 조용히 시작" 보다 훨씬 진단이 쉬워요.
함수 스코프 vs 블록 스코프 — var 의 마지막 함정
// 옛 함정 — for (var) 와 비동기
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 출력: 3, 3, 3 ← !! 모두 같은 i
// let 으로 바꾸면 매 반복마다 새 i
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 출력: 0, 1, 2
이게 ES6 가 let 을 만든 결정적 이유 중 하나. var 시절엔 IIFE 로 매번 새 스코프를 만들어야 했는데, 이젠 let 한 줄.
클로저 — 한 입만
function makeCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3
// count 는 외부에서 절대 못 봄 — 진짜 private
클로저 한 줄 정의. "함수가 자기 만들어진 스코프의 변수를 기억하고 함께 들고 다니는 것." makeCounter 가 반환된 뒤에도 count 가 살아있는 이유. 21편에서 깊이.
strict mode — 안전 모드
"use strict";
function f() {
undeclared = 5; // ❌ strict 면 에러, 아니면 글로벌 만듦
delete Object.prototype; // ❌ strict 면 에러
// ... 등 여러 위험 패턴 차단
}
ESM (import/export) 파일은 자동으로 strict입니다. 옛 스타일 스크립트만 명시 필요. 새 코드는 신경 안 써도 strict 입니다.
한 줄 요약 — 변수 작동의 모든 것
- 변수는 선언된 블록 안에서만 보임 (let/const).
- 함수와 var 는 호이스팅됨. let/const 는 TDZ 로 선언 전 사용 차단.
- 안에서 밖으로 차례로 변수를 찾음(스코프 체인).
- 함수가 외부 변수를 기억하면 그게 클로저.
- 3편의 결론 그대로 — const 가 기본, var 는 금지.
13편 — 에러 처리 try·catch·finally·throw
Error 객체, 커스텀 에러, 에러 다시 던지기.
이전: 11편 문자열 · 현재: 12편 (기초) · 다음 → 13편 에러 처리 · 진행: 12/26