JavaScript 전역 스코프 완전 가이드
“this.x는 되는데 this.y는 왜 undefined일까?”
코드를 작성하다가 이상한 현상을 발견했습니다.
var x = "global";
let y = "global";
console.log(this.x); // "global"
console.log(this.y); // undefined ???
“둘 다 전역에 선언했는데 왜 다르게 동작하나요?”
이 글에서는 MDN 공식 문서를 기반으로 전역 스코프의 정의와 var, let, const의 실제 동작 차이를 알아봅니다.
전역 스코프란?
공식 정의
MDN에 따르면:
“the global scope is the scope that contains, and is visible in, all other scopes.”
핵심:
- 전역 스코프는 모든 다른 스코프를 포함하고 있음
- 전역 스코프에 선언된 것은 모든 곳에서 접근 가능
- 가장 바깥쪽 실행 컨텍스트
클라이언트 사이드 JavaScript에서
MDN의 설명:
“In client-side JavaScript, the global scope is generally the web page inside which all the code is being executed.”
웹 페이지 내에서 실행되는 모든 코드는 같은 전역 스코프를 공유합니다.
// script1.js
var message = "Hello";
// script2.js
console.log(message); // "Hello" (접근 가능)
스코프의 정의와 계층
스코프란?
MDN의 정의:
“The scope is the current context of execution in which values and expressions are ‘visible’ or can be referenced.”
스코프는 값과 표현식이 “보이거나” 참조될 수 있는 현재 실행 컨텍스트입니다.
스코프의 종류
MDN에서 명시한 스코프 타입:
- Global scope: “The default scope for all code running in script mode.”
- Module scope: 모듈 모드에서 실행되는 코드에 적용
- Function scope: 함수 선언으로 생성
- Block scope: 중괄호로 생성 (let, const에만 적용)
스코프 계층 구조
MDN의 설명:
“Scopes can also be layered in a hierarchy, so that child scopes have access to parent scopes, but not vice versa.”
중요한 원칙:
- 자식 스코프는 부모 스코프에 접근 가능
- 부모 스코프는 자식 스코프에 접근 불가
- 전역 스코프는 최상위 부모
// 전역 스코프
const globalvar = "I'm global";
function outer() {
// outer 함수 스코프
const outer`var` = "I'm in outer";
function inner() {
// inner 함수 스코프
const inner`var` = "I'm in inner";
console.log(global`var`); // ✅ 접근 가능 (부모)
console.log(outer`var`); // ✅ 접근 가능 (부모)
console.log(inner`var`); // ✅ 접근 가능 (자신)
}
console.log(global`var`); // ✅ 접근 가능 (부모)
console.log(outer`var`); // ✅ 접근 가능 (자신)
console.log(inner`var`); // ❌ ReferenceError (자식)
}
console.log(global`var`); // ✅ 접근 가능 (자신)
console.log(outer`var`); // ❌ ReferenceError (자식)
console.log(inner`var`); // ❌ ReferenceError (자식)
전역 변수와 전역 객체
전역 변수의 본질
MDN의 설명:
“Global variables are in fact properties of the global object.”
전역 변수는 실제로 전역 객체의 속성입니다.
브라우저 환경
브라우저에서 전역 객체는 window입니다.
`var` name = "Alice";
console.log(window.name); // "Alice"
console.log(name === window.name); // true
globalThis
MDN 권장사항: 모든 환경에서 일관되게 globalThis 사용
// 브라우저
console.log(globalThis === window); // true
// Node.js
console.log(globalThis === global); // true
// 어디서든 동작
globalThis.myGlobal = "works everywhere";
var의 전역 스코프 동작
var와 전역 속성 생성
MDN의 명시:
“In a script, a variable declared using
varis added as a non-configurable property of the global object.”
핵심:
var로 선언한 전역 변수는 전역 객체의 속성이 됨- non-configurable: 삭제하거나 재정의 불가
`var` x = "global";
console.log(window.x); // "global"
console.log(globalThis.x); // "global"
// 삭제 불가
delete window.x;
console.log(window.x); // 여전히 "global"
함수 스코프와 블록
MDN의 설명에 따르면, var는 다음과 같이 동작합니다:
“variables created with
varare not block-scoped, but only local to the function (or global scope) that the block resides within.”
var는 블록 스코프를 무시합니다:
// 블록 안에서 `var` 선언
if (true) {
`var` block`var` = "I leak out";
}
console.log(block`var`); // "I leak out" (접근 가능!)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// for 루프
for (`var` i = 0; i < 3; i++) {
// ...
}
console.log(i); // 3 (루프 밖에서도 접근 가능)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// try-catch
try {
`var` error = "error message";
} catch (e) {
// ...
}
console.log(error); // "error message" (접근 가능)
모듈 환경에서의 차이
MDN의 중요한 구분:
“In both NodeJS CommonJS modules and native ECMAScript modules, top-level variable declarations are scoped to the module, and are not added as properties to the global object.”
모듈에서는 다르게 동작:
// script 모드 (전통적 <script> 태그)
`var` x = "global";
console.log(window.x); // "global"
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// module 모드 (<script type="module">)
`var` x = "module-scoped";
console.log(window.x); // undefined (전역 객체에 추가 안 됨)
var의 호이스팅
MDN의 설명:
“
vardeclarations are processed before code execution”
그러나:
“only the declaration is hoisted, not the initialization”
console.log(x); // undefined (선언은 호이스팅됨)
`var` x = 5;
console.log(x); // 5
// 실제로는 이렇게 동작
`var` x; // 선언만 최상단으로
console.log(x); // undefined
x = 5; // 초기화는 원래 위치에서
console.log(x); // 5
let과 전역 스코프
핵심 차이점
MDN의 명확한 설명:
“
letdeclarations do not create properties onglobalThiswhen declared at the top level of a script.”
let은 전역 객체의 속성을 만들지 않습니다.
실제 동작 비교
MDN이 제공하는 예제:
// `var`: 전역 객체의 속성이 됨
`var` x = "global";
console.log(this.x); // "global"
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// let: 전역 객체의 속성이 되지 않음
let y = "global";
console.log(this.y); // undefined
더 명확한 예제:
// 전역 스코프에서
`var` `var`Global = "I'm on window";
let letGlobal = "I'm not on window";
const constGlobal = "I'm not on window either";
console.log(window.`var`Global); // "I'm on window"
console.log(window.letGlobal); // undefined
console.log(window.constGlobal); // undefined
// 하지만 모두 전역에서 접근 가능
console.log(`var`Global); // "I'm on window"
console.log(letGlobal); // "I'm not on window"
console.log(constGlobal); // "I'm not on window either"
블록 스코프
MDN의 설명:
“Blocks only scope
letandconstdeclarations, but notvardeclarations.”
// let과 const는 블록 스코프를 따름
if (true) {
let blockLet = "trapped in block";
const blockConst = "also trapped";
`var` block`var` = "I escape";
}
console.log(block`var`); // "I escape"
console.log(blockLet); // ReferenceError
console.log(blockConst); // ReferenceError
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// for 루프
for (let i = 0; i < 3; i++) {
// i는 블록 안에만 존재
}
console.log(i); // ReferenceError
for (`var` j = 0; j < 3; j++) {
// j는 전역으로 누출
}
console.log(j); // 3
실전 예제
예제 1: 다양한 스코프 선언 비교
// 전역 스코프
`var` global`var` = "`var` in global";
let globalLet = "let in global";
const globalConst = "const in global";
// 전역 객체 확인
console.log(window.global`var`); // "`var` in global"
console.log(window.globalLet); // undefined
console.log(window.globalConst); // undefined
// 하지만 모두 전역에서 접근 가능
console.log(global`var`); // "`var` in global"
console.log(globalLet); // "let in global"
console.log(globalConst); // "const in global"
// globalThis로도 확인
console.log(globalThis.global`var`); // "`var` in global"
console.log(globalThis.globalLet); // undefined
console.log(globalThis.globalConst); // undefined
예제 2: 함수 내부에서 전역 접근
MDN의 설명에 따르면:
“a variable defined exclusively within the function cannot be accessed from outside the function”
하지만 반대는 가능합니다:
// 전역 변수
const globalMessage = "Hello from global";
function showMessage() {
// 함수 내부에서 전역 변수 접근 가능
console.log(globalMessage); // "Hello from global"
// 함수 지역 변수
const localMessage = "Hello from function";
console.log(localMessage); // "Hello from function"
}
showMessage();
// 전역에서 지역 변수 접근 불가
console.log(localMessage); // ReferenceError
예제 3: 블록 스코프와 전역
// 전역
let globalCount = 0;
if (true) {
// 블록 스코프
let blockCount = 10;
// 블록 내에서 전역 접근 가능
globalCount++;
console.log(globalCount); // 1
console.log(blockCount); // 10
}
// 전역에서 블록 스코프 접근 불가
console.log(globalCount); // 1
console.log(blockCount); // ReferenceError
예제 4: 호이스팅 차이
// `var`: 호이스팅되어 undefined
console.log(`var`Value); // undefined
`var` `var`Value = "`var` is hoisted";
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// let: TDZ (Temporal Dead Zone)로 에러
console.log(letValue); // ReferenceError
let letValue = "let is not hoisted the same way";
예제 5: 다중 윈도우/프레임
MDN의 언급:
“You can access [global variables] from different windows or frames using notation like
parent.phoneNumber.”
<!-- parent.html -->
<script>
`var` phoneNumber = "123-456-7890";
</script>
<iframe src="child.html"></iframe>
<!-- child.html -->
<script>
// 부모 윈도우의 전역 변수 접근
console.log(parent.phoneNumber); // "123-456-7890"
</script>
정리: var vs let vs const
전역 스코프에서의 차이
| 특성 | var |
let | const |
|---|---|---|---|
| 전역 객체 속성 생성 | ✅ Yes | ❌ No | ❌ No |
| 블록 스코프 | ❌ No | ✅ Yes | ✅ Yes |
| 호이스팅 (선언) | ✅ Yes | ✅ Yes* | ✅ Yes* |
| 호이스팅 (초기화) | ✅ undefined | ❌ TDZ | ❌ TDZ |
| 재선언 가능 | ✅ Yes | ❌ No | ❌ No |
| 재할당 가능 | ✅ Yes | ✅ Yes | ❌ No |
*let과 const도 호이스팅되지만 TDZ(Temporal Dead Zone) 때문에 선언 전 접근 시 에러
권장 사항
MDN의 암묵적 권장사항 (최신 문서 구조 기반):
- const 우선: 변경하지 않을 값
- let: 변경이 필요한 값
var지양: 레거시 코드가 아니라면 사용하지 않음
// ✅ 현대적 방식
const API_URL = "https://api.example.com"; // 변경 없음
let userCount = 0; // 변경 필요
// ❌ 구식 방식
`var` API_URL = "https://api.example.com";
`var` userCount = 0;
스크립트 모드 vs 모듈 모드
Script 모드 (전통적)
<script>
`var` x = "global";
console.log(window.x); // "global"
</script>
Module 모드
MDN의 설명:
“top-level variable declarations are scoped to the module, and are not added as properties to the global object.”
<script type="module">
`var` x = "module-scoped";
console.log(window.x); // undefined
// 모듈 내에서만 접근 가능
console.log(x); // "module-scoped"
</script>
체크리스트
전역 스코프 이해도 확인
[ ] 전역 스코프가 모든 스코프의 부모임을 이해했는가?
[ ] `var`는 전역 객체의 속성을 만들지만 let/const는 아님을 알고 있는가?
[ ] 블록 스코프와 함수 스코프의 차이를 설명할 수 있는가?
[ ] 스코프 계층 구조 (부모→자식 접근만 가능)를 이해했는가?
[ ] 호이스팅과 TDZ의 차이를 알고 있는가?
[ ] 스크립트 모드와 모듈 모드의 차이를 이해했는가?
[ ] globalThis를 사용하여 환경 독립적 코드를 작성할 수 있는가?
흔한 오해
오해 1: “let도 전역 변수다”
❓ 오해:
let x = "global";
// x는 전역 변수가 아니다?
✅ 사실:
let x = "global";
// x는 전역 스코프에 있지만
// 전역 객체(window)의 속성은 아니다
console.log(x); // "global" (전역에서 접근 가능)
console.log(window.x); // undefined (window 속성은 아님)
오해 2: “블록 안 var는 블록 스코프”
❓ 오해:
if (true) {
`var` x = "block";
}
// x는 블록 밖에서 접근 불가?
✅ 사실:
if (true) {
`var` x = "block";
}
console.log(x); // "block" (접근 가능!)
// `var`는 블록 스코프를 무시함
오해 3: “호이스팅은 var만”
❓ 오해:
// let은 호이스팅 안 됨?
✅ 사실:
// let도 호이스팅되지만 TDZ로 인해
// 선언 전 접근 시 에러 발생
{
console.log(x); // ReferenceError (TDZ)
let x = 5;
}
마치며
전역 스코프의 핵심 (MDN 기반):
- 정의: “the scope that contains, and is visible in, all other scopes”
- 계층: 자식→부모 접근 가능, 부모→자식 불가
var: 전역 객체 속성 생성, 블록 무시- let/const: 전역 객체 속성 생성 안 함, 블록 스코프 준수
- globalThis: 모든 환경에서 일관된 전역 객체 접근
실전 권장사항:
- const 우선, let 차선,
var지양 - 전역 변수 최소화
- 모듈 시스템 활용
- globalThis로 환경 독립적 코드 작성
참고 자료 (MDN 공식 문서)
스코프 관련
- Global scope - 전역 스코프 정의
- Scope - 스코프 기본 개념
- variable scope - 변수 스코프 가이드
키워드별
관련 개념
- Global object - 전역 객체
- Global variable - 전역 변수
- Hoisting - 호이스팅
관련 문서
- JavaScript 함수 선언 방식 비교: function vs 화살표 함수 - 함수 스코프와 this
- JavaScript 스코프 관리: 전역 변수 vs IIFE 모듈 패턴 - 전역 스코프 오염 방지
- JavaScript 호이스팅 완전 가이드 -
var, let, const 호이스팅 차이 - JavaScript 클로저 - 스코프 체인과 클로저
댓글