[JavaScript] Scope

2025. 11. 11. 16:30JavaScript

이 글에서는 JavaScript의 Scope를 중심으로 전역 / 함수 / 블록 스코프의 차이를 살펴보고, var와 let/const 키워드가 스코프를 어떻게 결정하는 지 학습하고, 서로 다른 스코프에서 발생하는 오류를 해결하는 방법을 공유합니다.

  • Scope
  • 전역 스코프 (Global Scope)
  • 함수 스코프 (Function Scope)
  • 블록 스코프 (Block Scope)
  • 스코프 문제 코드 예시
  • 회고

Scope

변수/함수에 접근할 수 있는 범위

JS 엔진은 어떤 변수를 찾을 때, 전체 코드를 탐색하는 것이 아닌 정해진 규칙(ex: 스코프)에 따라 탐색합니다.

[ Scope 사용 이유 ]

  • 변수 충돌 방지 : 서로 다른 스코프에서 동일한 이름의 변수를 독립적으로 사용할 수 있습니다.
  • 메모리 효율성 : 유효하지 않은 스코프가 메모리를 차지하는 것을 방지합니다.
  • 보안 및 캡슐화
  • 코드의 가독성 및 유지 관리성 향상

[ Scope 종류 ]

  • 전역 스코프 (Global Scope)
  • 함수 스코프 (Function Scope)
  • 블록 스코프 (Block Scope)

전역 스코프 (Global Scope)

프로그램 전체에서 접근 가능한 범위

전역 스코프에 선언된 변수/함수는 어디서든 사용 가능하지만, 네임스페이스 충돌 및 예기치 않은 값 변경에 주의해야 합니다.

[ 웹 브라우저에서의 var ]

웹 브라우저에서 let, const로 선언된 전역 변수와는 다르게, var는 브라우저의 전역 객체인 window의 속성으로 등록 됩니다.

<body>
    <script>
        var globalVar = "Hello World!";
    </script>
    <script>
        console.log(globalVar);     // "Hello World!"

        var globalVar = "Bye World";
        console.log(globalVar);     // "Bye World"
    </script>
</body>

<script type=”module”>를 통해, 불러올 JS 코드의 전역 변수가 모듈 스코프에 속하도록 설정해 전역 공간의 오염을 막을 수 있습니다.

React/Vue/Svelt 등의 ESM 기반 개발은 이러한 var의 전역 스코프 오염으로부터 안전합니다.


함수 스코프 (Function Scope)

함수 내에서만 접근 가능한 범위

var는 함수 내부에 선언될 경우 함수 스코프를 가지며, 함수 외부에 선언될 경우 전역 스코프를 가집니다.

{ // 블록 스코프
  var a = 1;    // 함수 외부에 선언되어 블록 스코프를 무시하고, 전역 스코프가 됩니다.
}
console.log(a); // 2

() => {
  var b = 20;
}
console.log(a); // 2
console.log(b); // ReferenceError

블록 스코프 (Block Scope)

중괄호( { } )로 둘러싸인 블록 내에서만 접근 가능한 범위

블록 내 let, const 선언 시 블록 스코프를 가지며, var가 함수 밖을 벗어나면 블록을 무시하고 전역 스코프를 가져 발생하는 문제를 예방할 수 있습니다.

이를 통해 if, for, while 등은 기존보다 직관적이고 안전한 독립적인 스코프를 형성할 수 있습니다.

{ // 블록 스코프
  let a = 1;
  var b = 2;    // 함수 외부에 선언되어 블록 스코프를 무시하고, 전역 스코프가 됩니다.
}
console.log(a); // ReferenceError
console.log(b); // 2

() => {
  let aa = 10;
  var bb = 20;
}
console.log(aa); // ReferenceError
console.log(bb); // ReferenceError

스코프 문제 코드 예시

다음과 같이 스코프가 다를 때 예측하기 어려운 결과가 발생할 수 있습니다.

for (var i = 0; i < 3; i++) {
	setTimeout(() => console.log(i), 100);
}
// 3 3 3
  1. var가 전역 스코프를 가진다.
  2. for 루프는 동기적으로 실행되고, 각 루프의 setTimeout는 대기
  3. MacroTaskQueue에서 대기 중이던 setTimeout 콜백이 CallStack에서 올라와 실행
  4. (2)에 의해 3으로 증가한 전역 변수 i를 출력한다.

이를 해결하기 위해서는

[ var 대신 let 사용 ]

let은 루프의 각 반복마다 새로운 렉시컬 환경을 생성하고, 콜백 함수가 해당 환경을 클로저로 캡쳐합니다.

동기 작업이 끝난 후 let 변수의 블록 스코프는 끝나도, 반복 당시의 i값이 보존된다.

 

[ 즉시 실행 함수(IIFE) 로 감싸고, i를 인자로 넘겨주기 ]

for 루프 안에서 IIFE를 사용하여 i 값을 매개변수로 넘기면, 각 반복마다 새로운 함수 스코프가 생성됩니다.

콜백은 이 스코프 내부 변수(x)를 클로저로 캡처하므로 반복 당시의 값이 보존된다.

for (var i = 0; i < 3; i++) {
  (function(x) {
    setTimeout(() => console.log(x), 100);
  })(i);
}

회고

이번 학습을 통해 함수형 프로그래밍에서의 스코프는, 단순히 “변수가 유효한 범위”로 이해하는 것은 충분하지 않다는 것을 느꼈다.

특히 JavaScript에서 스코프는 싱글 스레드 실행 모델, 비동기 처리 흐름(Event Loop), 그리고 클로저를 통한 상태 캡쳐 등과 긴밀하게 연결되어 있으며, 이를 함께 이해했을 때 정확한 동작 방식이 보였다.

따라서 “스코프를 안다”는 것은 “언어가 코드를 어떻게 해석하고 메모리에서 어떻게 유지하는지를 이해한다”라는 것이라 생각한다.

앞으로 함수형 패턴이나 비동기 로직을 작성할 때, 이러한 개념적 기반 위에서 더 명확한 의도를 갖고 코드를 작성할 필요성을 느꼈다.

'JavaScript' 카테고리의 다른 글

[JavaScript] Prototype  (0) 2025.11.14
[JavaScript] Execution Context  (0) 2025.11.11
[JavaScript] Promise  (0) 2025.11.07
[JavaScript] Callback Function  (0) 2025.11.06
[JavaScript] 블로킹 논블로킹, 동기 비동기  (0) 2025.11.06