1. 변수 섀도잉(Variable Shadowing)이란?
섀도잉은 같은 이름을 가진 변수가 내부 스코프(inner scope) 에서 외부 스코프(outer scope) 의 변수를 가려버리는 현상입니다.
마치 그림자(shadow)처럼 외부 변수를덮어버린다고 헤서 섀도잉이라고 부릅니다.
- 예제
let message = "외부";
function greet() {
let message = "내부"; // 외부 message를 섀도잉
console.log(message); // "내부"
}
greet();
console.log(message); // "외부"
-
함수 안과 밖의 message는 완전히 다른 변수입니다.
-
함수 안에서는 외부 message에 접근할 수 없습니다.
-
외부 변수는 영향받지 않습니다.
2. 중첩된 스코프에서의 섀도잉
스코프가 여러 단계로 중첩되면 각 레벨에서 섀도잉이 발생할 수 있습니다.
let a = 1; // Level 1: 전역
function outer() {
let a = 2; // Level 2: outer - 전역 a 섀도잉
console.log(a); // 2
function inner() {
let a = 3; // Level 3: inner - outer a 섀도잉
console.log(a); // 3
}
inner();
console.log(a); // 2
}
outer();
console.log(a); // 1
스코프 체인 탐색 순서
-
현재 스코프에서 변수 찾기
-
찾았다면 즉시 사용하고 탐색 종료
-
없다면 한 단계 바깥 스코프로 이동
-
전역까지 갔는데 없으면 ReferenceError 발생
3. 전역 언섀도잉(Global Unshadowing)
전역 언섀도잉(Global Unshadowing)이란 섀도잉으로 가려진 전역 변수에 다시 접근하는 기법입니다. JavaScript에서는 window 객체를 통해 가능합니다.
var로 선언한 전역 변수
- var로 선언한 전역 변수는 자동으로 window 객체의 속성이 됩니다.
var globalVar = "전역"; // window.globalVar로도 접근 가능
function test() {
var globalVar = "로컬"; // 전역 변수 섀도잉
console.log(globalVar); // "로컬"
console.log(window.globalVar); // "전역" ← 우회 접근!
}
test();
var - window 속성 가능
var x = "전역";
console.log(window.x); // "전역"
function test() {
var x = "로컬";
console.log(x); // "로컬"
console.log(window.x); // "전역"
}
let/const - window 속성 불가
let y = "전역";
console.log(window.y); // undefined
function test() {
let y = "로컬";
// window로 접근 불가!
}
중요: let과 const로 선언한 전역 변수는 window 객체의 속성이 되지 않습니다. 이는 전역 네임스페이스 오염을 방지하기 위한 의도적인 설계입니다.
4. 금지된 섀도잉(Illegal Shadowing)
금지된 섀도잉(Illegal Shadowing)은 JavaScript에서 허용되지 않는 섀도잉 패턴입니다. 특정 조합은 문법 오류를 발생시킵니다.
섀도잉 규칙 요약

허용되는 섀도잉
// 1. let을 let으로 섀도잉
let x = 1;
{
let x = 2; // O
console.log(x); // 2
}
console.log(x); // 1
// 2. var를 let으로 섀도잉
var y = 1;
{
let y = 2; // O
console.log(y); // 2
}
console.log(y); // 1
// 3. 함수 매개변수로 섀도잉
let z = 1;
function test(z) {
// O
console.log(z);
}
test(2); // 2
금지된 섀도잉
// let을 var로 섀도잉 시도
let x = 1;
{
var x = 2; // SyntaxError!
}
// const를 var로 섀도잉 시도
const y = 1;
{
var y = 2; // SyntaxError!
}
왜 let/const를 var로 섀도잉할 수 없을까?
-
var는 블록 스코프를 무시하고 함수 스코프만 인식합니다
-
var x 선언은 블록 밖으로 “호이스팅”되어 함수 최상단으로 이동합니다
-
결과적으로 같은 스코프에 let x와 var x가 공존하게 됩니다
-
JavaScript는 같은 스코프에서 같은 이름으로 두 번 선언하는 것을 금지합니다
5. 복사와 접근
섀도잉을 활용하여 외부 변수의 값을 복사하거나, 원본을 보호하면서 작업할 수 있습니다. 이는 실전에서 매우 유용한 패턴입니다.
5-1. 값 복사 vs 참조 접근
값 복사 (섀도잉으로 보호)
let count = 0;
function tempIncrement() {
let count = 0; // 섀도잉으로 복사
count++;
return count;
}
console.log(tempIncrement()); // 1
console.log(count); // 0 (보호됨)
참조 접근 (외부 변수 직접 사용)
let count = 0;
function increment() {
count++; // 외부 변수 직접 수정
return count;
}
console.log(increment()); // 1
console.log(count); // 1 (변경됨)
5-2. 원본 데이터 보호 패턴
섀도잉을 이용하면 원본 데이터를 보호하면서 안전하게 작업할 수 있습니다.
function processUserData(userData) {
// 섀도잉으로 복사본 생성 (원본 보호)
let userData = { ...userData };
// 이제 안전하게 수정 가능
userData.processed = true;
userData.timestamp = Date.now();
return userData;
}
const original = { name: "홍길동", age: 25 };
const processed = processUserData(original);
console.log(original); // { name: "홍길동", age: 25 } (원본 유지)
console.log(processed); // { name: "홍길동", age: 25, processed: true, ... }
주의: 위 예제는 의도적인 섀도잉입니다. 이런 경우 주석으로 명확히 표시하는 것이 좋습니다. 일반적으로는 다른 변수명을 사용하는 것이 더 명확합니다.
권장 패턴: 명확한 변수명 사용
function processUserData(originalData) {
// 변수명으로 의도를 명확히 표현
const processedData = { ...originalData };
processedData.processed = true;
processedData.timestamp = Date.now();
return processedData;
}
임시 계산 패턴
섀도잉을 활용하여 임시 계산을 수행하고 원본을 유지할 수 있습니다.
function createCalculator(initialValue) {
let value = initialValue;
return {
add: function (num) {
value += num;
return value;
},
// 임시 계산: 원본 보호
previewAdd: function (num) {
let value = this.getValue(); // 섀도잉으로 복사
value += num;
return value; // 임시 결과만 반환
},
getValue: function () {
return value;
},
};
}
const calc = createCalculator(10);
console.log(calc.add(5)); // 15 (원본 변경)
console.log(calc.previewAdd(100)); // 115 (임시 계산)
console.log(calc.getValue()); // 15 (원본 유지)
6. 클로저와 섀도잉
클로저(Closure)는 함수가 자신이 생성될 때의 환경을 기억하는 것입니다. 섀도잉과 결합되면 예상치 못한 동작이 발생할 수 있습니다.
- 올바른 클로저
function createCounter() {
let count = 0;
return function () {
count++; // 외부 접근
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
- 섀도잉 실수
function createCounter() {
let count = 0;
return function () {
let count = 100; // 섀도잉!
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 101
console.log(counter()); // 101 (증가 안됨)
6-1. 루프와 클로저의 함정
JavaScript 개발자들이 가장 많이 실수하는 패턴입니다. var의 함수 스코프 특성 때문에 발생합니다.
- 흔한 실수
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs.push(function () {
console.log(i);
});
}
funcs[0](); // 3 (예상: 0)
funcs[1](); // 3 (예상: 1)
funcs[2](); // 3 (예상: 2)
- 해결: let 사용
const funcs = [];
for (let i = 0; i < 3; i++) {
// let으로 변경!
funcs.push(function () {
console.log(i);
});
}
funcs[0](); // 0 ✓
funcs[1](); // 1 ✓
funcs[2](); // 2 ✓
왜 let을 사용하면 해결될까?
- let은 블록 스코프이므로 루프의 각 반복마다 새로운 i를 만듭니다. 각 함수는 자신만의 i를 클로저로 캡처하게 됩니다.
7. Oneul Code를 정리하며…
배운 내용 정리
-
섀도잉이란: 내부 스코프의 변수가 외부 스코프의 같은 이름 변수를 가리는 현상
-
허용되는 패턴: let→let, let→const, var→let, var→const
-
금지된 패턴: let→var, const→var (SyntaxError 발생)
-
전역 언섀도잉: var로 선언한 전역 변수는 window 객체로 접근 가능
-
복사와 접근: 섀도잉으로 원본 보호하며 작업 가능 (하지만 명확한 변수명 사용 권장)
-
주의사항: 클로저에서 섀도잉하면 외부 변수 접근 불가, 루프에서 var 사용 시 문제 발생
Oneul Code는 오늘 배운 내용을 기록하며, 변수 섀도잉을 다룰 때 다음과 같은 실전 가이드를 권장합니다.
-
기본적으로 섀도잉을 피하세요.
-
명확한 변수명을 사용하세요. (rawData, processedData 등)
-
const를 우선 사용하세요.
-
스코프를 최소화하세요.
-
의도적인 섀도잉이라면, 반드시 주석으로 명시하세요.
변수 스코프와 섀도잉을 올바르게 이해하고 관리하면, 예측 가능한 코드 구조를 유지하면서도 유지보수성과 가독성을 모두 챙길 수 있습니다.