🧙🏻‍♀️ 프론트엔드 전직퀘스트/자바스크립트 딥다이브

스코프 마을의 비밀 파헤치기 (var, let, const)

Orra.o 2026. 2. 4. 22:11

* 본 내용은 자바스크립트 딥다이브 13장, 15장을 학습한 내용입니다.

 

네 알겠습니다! 저한테 맡겨주세요!!

 

 

 

 

브루스 박사가 정리해 놓은 문서를 읽어보면서

var 키워드에 어떤 문제가 있고, 새로운 키워드 let과 const가 문제를 어떻게 해결해주는지 살펴봅시다.

 

.

.

.

.

 

 

🍄 스코프와 스코프 체인 🍄

 

스코프는 곧 '유효범위'입니다. 

 

우선 알아야 할 것은 '함수 스코프와' '블록 스코프' 입니다.

이를 풀어서 생각하면 함수 범위 그리고 블록 범위가 되겠네요.

 

function funcScope() {
  console.log('함수 스코프입니다.');
}

funcScope();

 

    ↑  위 코드는 블록 { }함수로 감싸져 있습니다. 즉 이는 함수 스코프입니다.

 

{
    var myPostion = "빨간포션";
}

if (true) {
    var monster = "주황버섯";
    console.log(monster);
}

for (var i = 0; i < 3; i++) {
    console.log("현재 몬스터 번호:", i);
}

   

↑  그 밖의 함수로 감싸져 있지 않은 다른 모든 블록 { } 으로 감싸져 있는 경우, 이는 블록 스코프입니다.

         예를 들어 if 문, for 문, while 문, try/catch 문 등이 해당되겠네요.

 

함수 스코프든, 블록 스코프든 스코프는 자신만의 지역을 만듭니다. 이를 지역 스코프라고 합니다.

추가로, 블록이 중첩이 되어도 지역 스코프 안에 또 다른 지역 스코프가 생깁니다.

 

어느 지역에도 속하지 않는 코드 바깥쪽의 스코프는 전역 스코프라고 합니다.

전역에 변수를 선언한다면, 자연스럽게 이 변수는 전역 스코프를 갖게 되겠지요?

 

그럼 지역 스코프가 중요한 이유가 무엇일까요?

바로 변수를 참조할 수 있는 유효범위가 지역 스코프 내로 한정되기 때문입니다.

 

다음 예시를 살펴봐요.

var a = "global a";
var b = "global b";

function outer() {
    var c = "outer's local c";
    
    function inner() {
    	var d = "inner's local d";
    }
}

 

변수 a, b는 전역 스코프를 갖고

변수 c는 outer 함수 스코프(지역 스코프)를 갖고

변수 d는 inner 함수 스코프(지역 스코프)를 갖습니다.

 

또한, 위 코드에서는 현재 outer 함수 내에 inner 함수가 중첩되어 있어요.

여기서 스코프 체인이라는 개념을 짚고 넘어갑시다.

지역 스코프가 중첩될 때는 계층적으로 구조를 갖는데요, 쉽게 말해 여기서 outer 함수는 inner 함수의 상위 스코프라고 합니다.

그리고 outer 함수의 상위 스코프는 전역 스코프가 되겠네요.

 

중요한 점은, 변수를 참조할 때 자바스크립트 엔진이 스코프 체인을 통해 상위 스코프 방향으로 이동하면서 변수를 찾는다는 것이에요.

그럼 inner 스코프 안에서 변수 c를 참조할 수 있겠군요? 상위 스코프로 이동하면 되니까요!

 

 

저 사다리는 오직 올라갈 수밖에 없는 구조예요....

따라서 상위 스코프에서 선언한 변수는 하위 스코프에서 참조할 수 있지만

하위 스코프에서 선언한 변수는 상위 스코프에서 참조하지 못하겠네요.

전역에서 outer 스코프 안의 c 변수를 참조할 수는 없어요!

 

 


🍄  지역 스코프와 렉시컬 환경 🍄

 

지역 스코프가 중요한 이유, 

바로 변수를 참조할 수 있는 유효범위가 지역 스코프 내로 한정되기 때문이었죠?

예시를 더 살펴봅시다.

 

다음 코드에서 각각 어떤 몬스터가 출력될까요?

var x = 'global monster';

function myFunc() {
    var x = 'local monster';
    console.log(x); // ① 어떤 몬스터가 출력될까요?
}

myFunc();

console.log(x); // ② 어떤 몬스터가 출력될까요?

 

 

console.log(x)를 맞딱뜨렸을 때, 자바스크립트 엔진은 두 개의 변수 x 중 어느 x를 참조해야 할 것인지를 결정하게 되는데요,

이를 식별자 결정이라고 합니다.

 

자바스크립트 엔진이 식별자 결정을 할 때면,

현재 코드가 어디서 실행되며, 주변에 어떤 코드가 있는지 ...를 파악하는데요, 이를 렉시컬 환경이라고 부릅니다.

 

즉, 위 예제에서 렉시컬 환경에 따라 myFunc() 함수 내부에서 선언된 x 변수myFunc() 함수 내부에서만 참조할 수 있고,

myFunc() 함수 외부에서는 참조할 수 없습니다. 따라서 ①번 콘솔에서는 local monster가 출력됩니다.

 

그럼 외부에서는 myFunc() 함수 안의 x 변수를 참조할 수 없으니

번 콘솔에서는 전역에 선언된 x 변수인 global monster가 출력되겠군요! 

 

 

와우 ~ 스코프 덕분에 같은 이름의 변수가 있어도 충돌이 일어나지 않네요. 스코프가 다르니까요.

그렇지만 같은 스코프 내에서는 절대 같은 변수 이름을 사용하면 안되겠지요? 

평행세계엔 또 다른 내가 있을수도 있겠지만 같은 세계에서는 도플갱어를 마주치면 죽잖아요..

 

⚠️충돌⚠️이 일어나서 오류가 발생할거에요.

 

 

그런데 말입니다.

function myFunc() {
    var x = 1;
    var x = 2;
    console.log(x); // 멀쩡하게 2 출력!
}
myFunc();

 

어라라라라라..? 같은 스코프 내에서 같은 변수를 썼는데 에러가 안 나네요?

아하, 이것이 var 변수의 문제점이군요!

 

좋아요, var 변수의 문제점을 정리해 봅시다.

 


🍄  var 키워드로 선언한 변수의 문제점 🍄

 

우선 방금 발견한 첫번째 문제점은

"var 키워드로 선언한 변수는 중복 선언이 가능하다" 입니다.

이때 나중에 선언한 변수로 인해, 이전 선언된 변수의 값이 변경되는 부작용이 발생합니다.

var x = 1;
var x = 100;

console.log(x); // 100 출력

 

그런데, 문제가 이것뿐만이 아니에요.

아까 함수 스코프와 블록 스코프 모두 지역 스코프이기 때문에 변수를 참조할 수 있는 유효범위가 지역 스코프 내로 한정된다고 했죠?

 

var monster = '주황버섯';

if (true) {
    var monster = '초록버섯';
    console.log(monster); // 초록버섯 (오케이)
}

console.log(monster); // 초록버섯 (오잉???)

 

위 코드를 살펴봅시다. 분명 if문블록 스코프지역 스코프를 가집니다.

그럼 마지막 줄 콘솔에서 monster를 출력했을 때 

if문 안에서의 monster 변수를 참조할 수가 없을텐데 말이죠 🤔 

 

지역 스코프 안의 초록버섯이 아니라 전역 스코프의 주황버섯으로 출력되어야 하는거 아닌가요? 

 

아하, 이것이 두번째 문제점이군요. var은 함수 레벨의 스코프는 잘 먹히지만, 블록 레벨의 스코프는 먹히지 않아요..

아무래도 말썽꾸러기 var 키워드는 함수로 묶어진 지역 스코프인

function myFunc ( ) { ... }  →  이러한 형태로만 가둬둘 수 있나봐요..

 

두번째 문제점 정리 :

"var 키워드로 선언한 변수는 오직 함수 스코프만을 지역 스코프로 인정한다."

 

 

우리에게는 1. 중복 선언이 되지 않으면서 2. 모든 지역 스코프에서 얌전히 가둬둘 수 있는 새로운 변수가 필요해요!

브루스 박사님이 새롭게 연구한 let, const 변수가 이를 해결해줄거에요.

 


🍄  let, const 키워드로 var 키워드의 문제를 해결하자! 🍄

 

정말 문제가 해결이 되는지 let 키워드로 중복 선언을 해봅시다.

let monster = "주황버섯"
let monster = "초록버섯" // 🚨 SyntaxError: Identifier 'monster' has already been declared

 

var 키워드로 중복선언했을 때는 에러가 나지 않고 새롭게 덮어씌워졌었는데,

let 키워드로 중복선언을 하니까 에러가 나네요!

 

 

그럼 블록 레벨 스코프가 먹히지 않았던 var 키워드의 문제도 let 키워드가 해결하는지 한번 테스트 해봅시다.

let monster = '주황버섯';

{
    let monster = '초록버섯';
    let postion = '빨간물약';
}

console.log(monster); // 주황버섯
console.log(postion); // 🚨 ReferenceError: postion is not defined

 

오호, let 키워드로 선언된 변수는 블록 레벨 스코프가 잘 먹히는군요!

전역에서 코드 블록 내의 postion 변수에 접근할 수 없어요.

 

엇, 근데 브루스 박사님은 두 개의 키워드를 주셨어요.

const 키워드가 있었죠. 그럼 let 키워드와 const 키워드의 차이는 무엇일까요?

 

 

우선 그 전에 선언초기화 개념을 간단하게 알아둡시다.

쉽게 말해 선언은 변수를 등록하는 것, 초기화는 변수에 값을 할당하는 것입니다. 

// 선언
let monster;

// 초기화
monster = '주황버섯'

// 한번에 선언과 초기화하기
let monster = '주황버섯'

 

쉽죠? 선언과 초기화는 변수 호이스팅 개념과 밀접하게 연결됩니다.

 

⬇️ 자세한 내용은 변수 호이스팅 글에서 다뤄볼게요! 

<아직 안씀>

 


🍄  let 키워드와 const 키워드의 차이를 알아보자! 🍄

 

그럼 각각의 키워드로 변수를 선언해 봅시다.

var monster;
let postion;
const skill; //🚨 SyntaxError: Missing initializer in const declaration

 

 

오잉! const 키워드에서만 에러가 납니다. 

const skill = '여제의축복';

 

그런데 선언과 동시에 초기화를 해주면 에러가 나지 않네요!

알아둡시다.

 

 1. const 키워드로 선언한 변수는 반드시 선언과 동시에 초기화해야 한다.

 

 

근데 바로 위 예제 코드에서 저는 장착 스킬을 바꾸고 싶어요.

저는 여제의축복에서 스피드듀얼샷으로 바꿀래요.

const skill = '여제의축복';
skill = '스피드듀얼샷'; //🚨 TypeError: Assignment to constant variable.

 

오잉! 스킬을 바꾸려고 했을 뿐인데 에러가 나네요.

그렇군요. const 키워드로 선언한 변수는 재할당을 할 수 없어요.

var 키워드, let 키워드는 재할당이 가능하지만, const 키워드는 불가능해요.

알아둡시다.

 

2. const 키워드로 선언한 변수는 재할당이 금지된다.

 

그렇지만 재할당이 안될 뿐, 원래 선언된 변수가 값을 변경할 수 있는 값이라면 (객체같이)

재할당 없이 접근하여 값을 변경할 수 있어요.

const monster = {
    name: '주황버섯',
    level: 8,
    damage: 42
};

monster.damage = 100;

 

위 예제 코드의 경우, 몬스터의 damage가 42 → 100으로 잘 바뀐답니다. 

그렇지만 몬스터 변수에 새로운 객체를 할당했다면 에러가 났을거에요.

 

 

 


🍄  자바스크립트는 렉시컬 스코프를 따른다!  🍄

 

마지막으로 짚고 넘어갈 개념은 렉시컬 스코프(정적 스코프)예요.

다음 코드를 한번 살펴봅시다.

let monster = '주황버섯';

const myFuncOne = () => {
    let monster = '초록버섯';
    myFuncTwo();
}

const myFuncTwo = () => {
    console.log(monster);
}

myFuncOne(); // 초록버섯일까요 주황버섯일까요

 

화살표 함수 myFuncOne 안에서 myFuncTwo 함수를 호출하고 있어요.

그럼 myFuncOne 함수 호출 시에(마지막줄 코드 참고) myFuncTwo 함수가 실행되고, 변수 monster의 할당된 값을 출력해야 합니다.

 

여기서 살짝 헷갈릴 수 있는데요. 그럼 myFuncTwo 함수의 상위 스코프는

myFuncOne 함수 스코프일까요 아니면 전역 스코프일까요?

상위 스코프가 myFuncOne 함수 스코프라면 초록버섯을,

상위 스코프가 전역 스코프라면 주황버섯을 출력할거에요.

 

이때, 두 가지의 결정 방법이 있어요. 


1. 함수 호출 위치에 따라 결정한다면?

--> myFuncOne 안에서 호출했으니 상위 스코프는 myFuncOne이겠네요.

 

첫번째 방식을 동적 스코프라고 합니다.

함수가 호출되는 시점에 동적으로 상위 스코프를 결정합니다.


2. 함수 정의 위치에 따라 결정한다면?

--> myFuncTwo 함수는 전역에 정의되어 있으니 상위 스코프는 전역이겠네요.

 

두번째 방식은 렉시컬 스코프 또는 정적 스코프라고 합니다.

함수 정의가 평가되는 시점에 이미 상위 스코프가 결정됩니다.


 

두 방법 중, 자바스크립트는 렉시컬 스코프를 따릅니다.

따라서  함수의 호출 위치가 아닌!!!  함수를 어디서 정의했는지에 따라 상위 스코프를 결정합니다.

호출된 위치는 상위 스코프 결정에 어떠한 영향도 주지 않아요.

함수가 정의될 때 생성된 함수 객체는 자신의 상위 스코프를 기억해 두고, 함수가 호출될 때 그 상위 스코프를 참조하는 것이죠.

 

 

 

 

.

.

.

.

.

 

 

마무리를 해볼까요?

우리는 스코프와 스코프 체인이 어떻게 동작하는지 알아보았고

중복 선언이 된다든지, 함수 스코프만 지역 스코프로 인정하는 var 키워드의 문제점을 파악했어요.

그리고 이 문제점을 해결하기 위한 새로운 키워드 let과 const의 차이까지 알아보았어요!

마지막으로 자바스크립트가 렉시컬 스코프를 따른다는 것까지 알았네요.

 

휴.. 이렇게 브루스 박사님의 부탁을 무사히 마친거 같네요!

 

 

✨ 프론트엔드 경험치 30xp 를 얻었습니다.