JavaScript·2026. 01. 27
모듈 스코프 vs 함수 스코프 - 실행 시점의 차이
이 문서를 읽기 전에 [[scope|스코프(Scope)]]를 먼저 읽는 것을 권장한다.
개요
JavaScript에서 변수를 어디에 선언하느냐에 따라 실행 시점이 달라진다. 특히 ES 모듈 시스템에서 모듈 스코프에 선언된 코드는 모듈이 처음 import될 때 한 번만 실행된다. SPA(Single Page Application)에서 이 차이를 이해하지 못하면 "간헐적으로 발생하는" 버그를 만들 수 있다.
모듈 스코프란
ES 모듈에서 함수 외부에 선언된 변수는 모듈 스코프에 속한다:
javascript
// myModule.js
const INIT_TIME = Date.now(); // 모듈 스코프
export function getInitTime() {
return INIT_TIME;
}
INIT_TIME은 이 모듈이 처음 import될 때 한 번 계산되고, 이후로는 그 값이 유지된다.
javascript
// app.js
import { getInitTime } from './myModule.js';
console.log(getInitTime()); // 1706345678901
// ... 10초 후
console.log(getInitTime()); // 1706345678901 (같은 값)
10초가 지나도 같은 값이다. 모듈이 다시 로드되지 않기 때문이다.
함수 스코프와의 차이
함수 내부에 선언하면 함수가 호출될 때마다 실행된다:
javascript
// myModule.js
export function getCurrentTime() {
const NOW = Date.now(); // 함수 스코프
return NOW;
}
javascript
// app.js
import { getCurrentTime } from './myModule.js';
console.log(getCurrentTime()); // 1706345678901
// ... 10초 후
console.log(getCurrentTime()); // 1706345688901 (다른 값)
호출할 때마다 새로운 값을 반환한다.
SPA에서 흔히 발생하는 실수
React, Vue 같은 SPA 프레임워크에서 페이지 이동은 실제 새로고침이 아니다. 컴포넌트가 마운트/언마운트될 뿐, 모듈은 다시 로드되지 않는다.
문제가 되는 코드
typescript
// ShotPage.tsx
const SHOT_TIME = getShotTimeFromURL(); // 모듈 스코프 - 한 번만 실행
export function ShotPage() {
const [time, setTime] = useState(SHOT_TIME);
// ...
}
getShotTimeFromURL()이 현재 URL을 읽어서 촬영 시간을 반환한다고 가정하자.
/event/a로 접속 → 모듈 로드,SHOT_TIME = 4/event/b로 이동 → 모듈은 이미 로드됨,SHOT_TIME = 4(그대로!)event/b는 10초여야 하는데 4초로 동작
올바른 코드
typescript
// ShotPage.tsx
export function ShotPage() {
const SHOT_TIME = getShotTimeFromURL(); // 함수 스코프 - 렌더링마다 실행
const [time, setTime] = useState(SHOT_TIME);
// ...
}
컴포넌트 내부에 선언하면 컴포넌트가 마운트될 때마다 실행된다.
언제 모듈 스코프를 써야 하나
모듈 스코프는 정말 한 번만 초기화해도 되는 값에 사용한다:
javascript
// 상수
const API_BASE_URL = 'https://api.example.com';
// 싱글톤 인스턴스
const logger = new Logger();
// 무거운 초기화 (한 번만 하고 싶을 때)
const heavyData = loadHeavyData();
반면, 런타임에 바뀔 수 있는 값에 의존하는 경우 모듈 스코프에 두면 안 된다:
javascript
// 나쁜 예 - URL은 런타임에 바뀔 수 있음
const CONFIG = getConfigFromURL();
// 나쁜 예 - 로그인 상태는 런타임에 바뀔 수 있음
const USER = getCurrentUser();
요약
| 스코프 | 실행 시점 | 적합한 용도 |
|---|---|---|
| 모듈 스코프 | import 시 한 번 | 상수, 싱글톤, 무거운 초기화 |
| 함수 스코프 | 함수 호출마다 | 런타임 의존 값, 동적 계산 |
변수 선언 위치는 단순한 코드 스타일이 아니라 실행 시점과 생명주기를 결정한다.