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에서 명시한 스코프 타입:

  1. Global scope: “The default scope for all code running in script mode.”
  2. Module scope: 모듈 모드에서 실행되는 코드에 적용
  3. Function scope: 함수 선언으로 생성
  4. 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 var is 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 var are 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의 설명:

var declarations 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의 명확한 설명:

let declarations do not create properties on globalThis when 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 let and const declarations, but not var declarations.”

// 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의 암묵적 권장사항 (최신 문서 구조 기반):

  1. const 우선: 변경하지 않을 값
  2. let: 변경이 필요한 값
  3. 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 기반):

  1. 정의: “the scope that contains, and is visible in, all other scopes”
  2. 계층: 자식→부모 접근 가능, 부모→자식 불가
  3. var: 전역 객체 속성 생성, 블록 무시
  4. let/const: 전역 객체 속성 생성 안 함, 블록 스코프 준수
  5. globalThis: 모든 환경에서 일관된 전역 객체 접근

실전 권장사항:

  • const 우선, let 차선, var 지양
  • 전역 변수 최소화
  • 모듈 시스템 활용
  • globalThis로 환경 독립적 코드 작성

참고 자료 (MDN 공식 문서)

스코프 관련

키워드별

  • var - var 선언 상세
  • let - let 선언 상세
  • const - const 선언 상세

관련 개념


관련 문서

댓글