
스코프란 식별자(변수명)가 유효한 범위를 말합니다. 즉, 변수를 어디서 참조할 수 있는지를 결정하는 규칙이에요.
JavaScript에서 스코프는 함수 단위로 생성되며, 이는 다른 언어와 구분되는 중요한 특징입니다.
var 전역변수 = '어디서든 접근 가능';
function 외부함수() {
var 외부변수 = '외부 함수 내부에서만 접근';
function 내부함수() {
var 내부변수 = '내부 함수에서만 접근';
console.log(전역변수); // ✅ 접근 가능
console.log(외부변수); // ✅ 접근 가능
console.log(내부변수); // ✅ 접근 가능
}
console.log(전역변수); // ✅ 접근 가능
console.log(외부변수); // ✅ 접근 가능
console.log(내부변수); // ❌ ReferenceError!
}
console.log(전역변수); // ✅ 접근 가능
console.log(외부변수); // ❌ ReferenceError!
console.log(내부변수); // ❌ ReferenceError!
var 전역변수 = 'I am global';
let 전역렛 = 'I am also global';
const 전역상수 = 'I am global constant';
function 어디서든접근() {
console.log(전역변수); // 'I am global'
console.log(전역렛); // 'I am also global'
console.log(전역상수); // 'I am global constant'
}
if (true) {
console.log(전역변수); // 'I am global'
console.log(전역렛); // 'I am also global'
console.log(전역상수); // 'I am global constant'
}
전역 스코프의 특징:
function 함수스코프예제() {
var 함수변수 = '함수 내부 변수';
let 함수렛 = '함수 내부 let';
const 함수상수 = '함수 내부 const';
if (true) {
var 블록안var = 'var는 함수 스코프';
let 블록안let = 'let은 블록 스코프';
const 블록안const = 'const도 블록 스코프';
}
console.log(함수변수); // ✅ '함수 내부 변수'
console.log(함수렛); // ✅ '함수 내부 let'
console.log(함수상수); // ✅ '함수 내부 const'
console.log(블록안var); // ✅ 'var는 함수 스코프'
console.log(블록안let); // ❌ ReferenceError!
console.log(블록안const); // ❌ ReferenceError!
}
함수스코프예제();
// 함수 밖에서는 접근 불가
console.log(함수변수); // ❌ ReferenceError!
핵심: var는 함수 스코프를 따르지만, let과 const는 블록 스코프를 따릅니다.
{
var 블록var = 'var는 블록을 무시';
let 블록let = 'let은 블록에 갇힘';
const 블록const = 'const도 블록에 갇힘';
}
console.log(블록var); // ✅ 'var는 블록을 무시'
console.log(블록let); // ❌ ReferenceError!
console.log(블록const); // ❌ ReferenceError!
// for문에서의 차이
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log('var:', i), 100); // 3, 3, 3
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log('let:', j), 100); // 0, 1, 2
}
스코프 체인이란 식별자를 찾기 위해 스코프를 연쇄적으로 탐색하는 메커니즘입니다.
var 전역변수 = '전역';
function 단계1() {
var 변수 = '단계1';
function 단계2() {
var 변수 = '단계2';
function 단계3() {
console.log(변수); // 어떤 값이 출력될까요?
}
단계3();
}
단계2();
}
단계1(); // '단계2' 출력
스코프 체인 탐색 과정:
1. 현재 스코프(단계3)에서 변수 찾기 → 없음
2. 상위 스코프(단계2)에서 변수 찾기 → 발견! ('단계2')
3. 더 이상 탐색하지 않고 해당 값 반환
var 이름 = '전역 철수';
function 외부함수() {
var 나이 = 30;
function 내부함수() {
var 취미 = '독서';
// 스코프 체인을 따라 변수 탐색
console.log(취미); // 내부함수 스코프에서 발견
console.log(나이); // 외부함수 스코프에서 발견
console.log(이름); // 전역 스코프에서 발견
// 없는 변수를 참조하면?
console.log(존재하지않는변수); // ReferenceError!
}
내부함수();
}
외부함수();
JavaScript는 렉시컬 스코프를 따릅니다. 이는 함수가 호출되는 위치가 아닌, 함수가 선언된 위치에 따라 스코프가 결정된다는 의미입니다.
var 글로벌 = '전역 변수';
function 함수1() {
var 지역 = '함수1 지역 변수';
함수2();
}
function 함수2() {
console.log(글로벌); // ✅ 접근 가능 (전역에서 선언되었으니)
console.log(지역); // ❌ ReferenceError! (함수2가 선언된 위치에서 지역 변수가 없음)
}
함수1();
핵심: 함수2가 함수1 내부에서 호출되었지만, 함수2는 전역에서 선언되었기 때문에 전역 스코프만 참조할 수 있습니다.
var 변수 = '전역';
function 렉시컬예제() {
var 변수 = '렉시컬';
function 내부함수() {
console.log(변수); // '렉시컬' - 선언 위치 기준
}
내부함수();
return 내부함수;
}
function 다른함수() {
var 변수 = '다른함수';
var 가져온함수 = 렉시컬예제();
가져온함수(); // 여전히 '렉시컬' - 호출 위치와 무관
}
다른함수();
console.log(변수); // undefined (선언은 호이스팅, 초기화는 안됨)
var 변수 = '할당됨';
console.log(변수); // '할당됨'
// 실제로는 이렇게 동작
var 변수; // undefined로 초기화 (호이스팅)
console.log(변수); // undefined
변수 = '할당됨';
console.log(변수); // '할당됨'
console.log(렛변수); // ❌ ReferenceError! (TDZ)
console.log(상수); // ❌ ReferenceError! (TDZ)
let 렛변수 = 'let 값';
const 상수 = 'const 값';
console.log(렛변수); // 'let 값'
console.log(상수); // 'const 값'
TDZ (Temporal Dead Zone): let/const는 선언 전까지 접근할 수 없는 영역이 존재합니다.
클로저는 스코프 체인을 활용한 JavaScript의 강력한 기능입니다.
function 외부함수(외부변수) {
function 내부함수(내부변수) {
console.log('외부변수:', 외부변수);
console.log('내부변수:', 내부변수);
}
return 내부함수;
}
const 클로저함수 = 외부함수('외부에서 전달');
클로저함수('내부에서 전달');
// 외부변수: 외부에서 전달
// 내부변수: 내부에서 전달
클로저의 실용적 예제:
function 카운터만들기() {
let 카운트 = 0;
return {
증가: function() {
카운트++;
return 카운트;
},
감소: function() {
카운트--;
return 카운트;
},
현재값: function() {
return 카운트;
}
};
}
const 카운터1 = 카운터만들기();
const 카운터2 = 카운터만들기();
console.log(카운터1.증가()); // 1
console.log(카운터1.증가()); // 2
console.log(카운터2.증가()); // 1 (독립적인 스코프)
console.log(카운터1.현재값()); // 2
console.log(카운터2.현재값()); // 1
// 문제가 있는 코드
var 버튼들 = [];
for (var i = 0; i < 3; i++) {
버튼들[i] = function() {
console.log('버튼', i, '클릭!'); // 모두 3을 출력
};
}
버튼들[0](); // '버튼 3 클릭!'
버튼들[1](); // '버튼 3 클릭!'
버튼들[2](); // '버튼 3 클릭!'
// 해결책 1: let 사용
const 올바른버튼들1 = [];
for (let j = 0; j < 3; j++) {
올바른버튼들1[j] = function() {
console.log('버튼', j, '클릭!');
};
}
올바른버튼들1[0](); // '버튼 0 클릭!'
올바른버튼들1[1](); // '버튼 1 클릭!'
올바른버튼들1[2](); // '버튼 2 클릭!'
// 해결책 2: 즉시실행함수(IIFE) 사용
const 올바른버튼들2 = [];
for (var k = 0; k < 3; k++) {
올바른버튼들2[k] = (function(인덱스) {
return function() {
console.log('버튼', 인덱스, '클릭!');
};
})(k);
}
올바른버튼들2[0](); // '버튼 0 클릭!'
올바른버튼들2[1](); // '버튼 1 클릭!'
올바른버튼들2[2](); // '버튼 2 클릭!'
const 모듈 = (function() {
// 비공개 변수들
let 비밀데이터 = '접근 불가';
let 카운터 = 0;
// 비공개 함수들
function 비공개함수() {
console.log('외부에서 호출 불가');
}
// 공개 API 반환
return {
공개메서드1: function() {
카운터++;
console.log('카운터:', 카운터);
},
공개메서드2: function() {
비공개함수(); // 내부에서는 호출 가능
return 비밀데이터.length;
},
getter: function() {
return 카운터;
}
};
})();
모듈.공개메서드1(); // '카운터: 1'
console.log(모듈.getter()); // 1
// 비공개 요소들은 접근 불가
console.log(모듈.비밀데이터); // undefined
모듈.비공개함수(); // TypeError!
// math.js 파일
const PI = 3.14159;
let 계산횟수 = 0;
function 원넓이(반지름) {
계산횟수++;
return PI * 반지름 * 반지름;
}
function 원둘레(반지름) {
계산횟수++;
return 2 * PI * 반지름;
}
// 공개할 것만 export
export { 원넓이, 원둘레 };
export { PI as 파이상수 };
// main.js 파일
import { 원넓이, 원둘레, 파이상수 } from './math.js';
console.log(원넓이(5)); // 78.53975
console.log(파이상수); // 3.14159
console.log(계산횟수); // ❌ ReferenceError! (비공개)
// 성능에 좋지 않은 예
function 성능나쁨() {
for (let i = 0; i < 1000; i++) {
// 반복문 안에서 함수 선언 (매번 새로 생성)
const 헬퍼함수 = function() {
return Math.random();
};
console.log(헬퍼함수());
}
}
// 성능에 좋은 예
function 성능좋음() {
// 함수를 반복문 밖에서 선언
const 헬퍼함수 = function() {
return Math.random();
};
for (let i = 0; i < 1000; i++) {
console.log(헬퍼함수());
}
}
// 스코프 체인 최적화
const 전역변수 = '전역값';
function 최적화예제() {
// 자주 사용되는 전역 변수를 지역 변수로 캐싱
const 로컬전역변수 = 전역변수;
for (let i = 0; i < 10000; i++) {
// 스코프 체인을 타고 올라가지 않아도 됨
console.log(로컬전역변수);
}
}
스코프의 기본 규칙:
1. 함수 스코프: var는 함수 단위로 스코프 생성
2. 블록 스코프: let/const는 블록 단위로 스코프 생성
3. 렉시컬 스코프: 함수 선언 위치에 따라 스코프 결정
4. 스코프 체인: 안쪽에서 바깥쪽으로 변수 탐색
호이스팅 특징:
기억하세요: 스코프는 코드 작성 시점에 결정되며, 실행 시점의 호출 위치와는 무관합니다!