[모딥다] 14장. 전역 변수의 문제점

vanLan·2026년 1월 30일

모딥다

목록 보기
10/25
post-thumbnail

📁 변수의 생명 주기

지역 변수의 생명 주기

  • 함수 내부에서 선언된 지역 변수는 함수가 호출되면 생성되고 함수가 종료하면 소멸.

  • 지역 변수의 생명 주기는 함수의 생명 주기와 대부분 일치.

    function foo() {
      var x = 'local';  // 변수 x 생성/할당
      console.log(x);
      return x;  // 변수 x 소멸
    }
    
    foo();
    console.log(x);  // ReferenceError: x is not defined
  • 호이스팅은 스코프를 단위로 동작함.

    var x = 'global';
    
    function foo() {
      console.log(x);  // undefined
      var x = 'local';
    }
    
    foo();
    console.log(x);  // global

전역 변수의 생명 주기

  • var 키워드로 선언한 전역 변수는 전역 객체의 프로퍼티가 됨. (전역 변수의 생명주기가 전역 객체의 생명 주기와 일치)

    ※ 전역 객체: 코드가 실행되기 이전 단계에 JS엔진에 의해 어떤 객체보다도 먼저 생성되는 특수한 객체.
    클라이언트 사이드: window, 서버사이드: global
    전역 객체는 표준 빌트인 객체(Object, String, Number, Function, Array, ...)와 환경에 따른 호스트 객체, var 키워드로 선언한 전역 변수와 전역 함수를 프로퍼티로 갖음.


📁 전역 변수의 문제점

암묵적 결합

  • 모든 코드가 전역 변수를 참조하고 변결할 수 있는 암묵적 결합이 허용됨.
  • 변수의 유효 범위가 크면 클 수록 코드의 가독성은 나빠지고 의도치 않게 상태가 변경될 수 있는 위험성도 높아짐.

긴 생명 주기

  • 전역 변수는 생명주기가 길기 때문에, 메모리 리소스도 오랜 기간 소모하게 됨.
  • 생명 주기가 긴 만큼 변수 이름이 중복될 가능성이 있어, 의도치 않은 재할당이 이뤄질 수 있음.

스코프 체인 상에서 종점에 존재

  • JS엔진이 스코프 체인을 따라 변수를 검색할 때 가장 마지막에 검색됨. (전역 변수의 검색이 가장 느림, 검색 속도의 차이는 그다지 크지 않지만 속도의 차이는 분명하게 있음)

네임스페이스 오염

  • JS는 파일이 분리되어 있다고 해도, 하나의 전역 스코프를 공유하게 됨.
  • 다른 파일 내에서 동일한 이름으로 명명된 전역 변수나 전역 함수가 같은 스코프 내에 존재할 경우 의도치 않은 결과를 가져올 수 있음.

📁 전역 변수의 사용을 억제하는 방법

  • 전역 변수를 반드시 사용해야할 이유가 없다면, 지역 변수를 사용.
  • 변수의 스코프는 좁을수록 좋음.

즉시 실행 함수

  • 함수 정의와 동시에 호출되는 즉시 실행 함수는 단 한번만 호출됨.

  • 모든 코드를 즉시 실행 함수로 감싸면 모든 변수는 즉시 실행 함수의 지역 변수가 됨.

    (function () {
      var foo = 10;  // 즉시 실행 함수의 지역 변수
    }());
    
    console.log(foo);  // ReferenceError: foo is not defined
  • 주로 라이브러리에서 사용됨.

네임스페이스 객체

  • 전역에 네임스페이스 역할을 담당할 객체를 생성하고 전역 변수처럼 사용하고 싶은 변수를 프로퍼티로 추가하는 방법.

  • 네임스페이스 객체에 또 다른 네임스페이스 객체를 프로퍼티로 추가해서 계층적으로 구성 가능.

    var MYAPP = {};  // 전역 네임스페이스 객체
    
    MYAPP.person = {
      name: 'Lee',
      address: 'Seoul'
    }
    
    console.log(MYAPP.person.name);  // Lee

모듈 패턴

  • JS의 강력한 기능인 클로저를 기반으로 동작함.
  • 모듈 패턴의 특징은 전역 변수의 억제는 물론 캡슐화까지 구현할 수 있다는 것임.

    ※ 캡슐화: 객체의 상태를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작할 수 있는 동작인 메서드를 하나로 묶는 것.
    객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용하기도 하는데 이를 정보 은닉이라 함.

    var Counter = (function () {
      // private 변수
      var num = 0;
      
      // 외부로 공개할 데이터나 메서드를 프로퍼티로 추가한 객체를 반환
      return {
        increase() {
          return ++num;
        },
        decrease() {
          return --num;
        }
      };
    }());
    
    // private 변수는 외부로 노출되지 않음
    console.log(Counter.num);  // undefined
    
    console.log(Counter.increase());  // 1
    console.log(Counter.decrease());  // 0

ES6 모듈

  • ES6 모듈은 파일 자체의 독자적인 모듈 스코프를 제공함.
  • 모듈 내에서 var 키워드로 선언한 변수는 더는 전역 변수가 아니며 전역 객체의 프로퍼티도 아님.

📁 의문점

  • 그렇다면, 현대의 Vite와 Webpack들은 모듈과 ES6 모듈을 어떻게 사용하는 걸까?
    • 개발 환경 (Vite): "진짜 ES6 모듈을 쓴다"
      • npm run dev시, Vite는 번들링을 하지 않음.
      • Native ESM: ES6 모듈을 그대로 사용(import/export)
      • 동작: 브라우저가
      • 스코프: 파일 자체가 독립적이므로, 브라우저가 ES6 모듈 스코프 규칙을 직접 적용함.
      • 개발 서버일 경우 브라우저가 모듈이므로 전역 변수를 만들지 않음.
    • 배포 환경: "모듈인척 연기한다"
      • npm run build시 성능을 위해 코드를 하나(또는 몇 개)의 파일로 합침. 이를 번들링이라 함.

      • 번들러는 파일을 합치면서 모듈 스코프가 유지되는 것처럼 코드를 변형.

      • 예시:

        // bundle.js (함수 껍데기를 벗기고 하나로 합침)
        
        // a.js의 내용
        var x$1 = 1;  // 충돌을 피하기 위해 뒤에 $1을 붙임
        
        // b.js의 내용
        var x$2 = 2;  // 여기는 $2를 붙임
        
        console.log(x$1 + x$2);
      • 결과: 겉으로 보기엔 그냥 쭉 이어진 코드 같지만, 번들러가 식별자를 바꿔달아서 논리적 모듈 스코프(격리)를 유지시킴.

      • 결론: 형태는 달라졌지만, 외부와 격리시킨다는 모듈 패턴 철학은 그대로 구현됨.

profile
프론트엔드 개발자를 꿈꾸는 이

0개의 댓글