모던 JS DeepDive(14장)

Minji Lee·2024년 1월 29일
0
post-thumbnail

[14장] 전역 변수의 문제점

⚠️ 전역 변수의 무분별한 사용은 위험!

14.1 변수의 생명 주기

14.1.1 지역 변수의 생명 주기

  • 변수는 선언에 의해 생성되고 할당을 통해 값을 갖고, 언젠가는 소멸 ⇒ 생명 주기 존재
  • 전역 변수의 생명 주기 = 애플리케이션의 생명 주기
function foo() {
  var x = "local";
  console.log(x); // lcoal
  return x;
}

foo();
console.log(x); // ReferenceError: x is not defined
  • 변수 선언은 선언문이 어디에 있든 상관없이 가장 먼저 실행됨. 즉, 변수 선언은 런타임에 실행되는 것이 아닌 런타임 이전 단계에서 JS 엔진에 의해 먼저 실행됨 ⇒ 전역 변수에 한정된 설명
  • 함수 내부에서 선언한 변수는 함수가 호출된 직후에 함수 몸체의 코드가 한 줄씩 순차적으로 실행되기 이전에 JS 엔진에 의해 먼저 실행됨

  • 지역 변수의 생명주기 = 함수의 생명 주기(지역 변수가 함수보다 오래 생존하는 경우도 존재)
    • 변수는 하나의 값을 저장하기 위해 확보한 메모리 공간 자체 또는 그 메모리 공간을 식별하기 위해 붙인 이름
    • 변수의 생명주기는 메모리 공간이 확보(allocate)된 시점부터 메모리 공간이 해제(release)되어 가용 메모리 풀에 반환되는 시전까지
  • 변수는 자신이 등록된 스코프가 소멸될 때까지 유효
  • 할당된 메모리 공간은 더 이상 그 누구도 참조하지 않을 때 가비지 콜렉터에 의해 해제되어 가용 메모리 풀에 반환됨 → 누군가 메모리 공간을 참조하고 있으면 해제되지 않고 확보된 상태로 남아있음
  • 스코프 역시, 누군가 스코프를 참조하고 있으면 스코프는 소멸하지 않고 생존, 함수가 종료하면 함수가 생성한 소코프도 소멸
var x = "global";

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

foo();
console.log(x); // global
  • 호이스팅은 스코프 단위로 동작

14.1.2 전역 변수의 생명 주기

[전역 변수 특징]

  1. 함수와 달리 전역 코드는 명시적인 호출 없이 실행됨. 즉, 특별한 진입점이 없고 코드가 로드되자마자 곧바로 해석되고 실행됨
  2. 함수는 함수 몸체의 마지막 문 또는 반환문이 실행되면 종료함. 하지만, 전역 코드에는 반환문을 사용할 수 없으므로 마지막 문이 실행되어 더 이상 실행할 문이 없을 때 종료

  • var 키워드로 선언한 전역 변수는 전역 객체의 프로퍼티가 됨 ⇒ 전역 변수의 생명 주기 = 전역 객체의 생명 주기

    전역 객체: 코드가 실행되기 이전 단계에 JS 엔진에 의해 어떤 객체보다도 먼저 생성되는 특수한 객체
    → window(클라이언트 사이드 환경=브라우저), global(서버 사이드 환경) 등(ES11 부터는 globalThis로 통일됨)
    → 표준 빌트인 객체(Object, String, Number, Function, Array,..), 호스트 객체, var 키워드로 선언한 전역 변수와 전연 함수를 프로퍼티로 가짐


14.2 전역 변수의 문제점

  1. 암묵적 결합
  • 전역 변수를 선언한 의도는 코드 어디서든 참조하고 할당할 수 있는 변수를 사용하겠다는 것 ⇒ 모든 코드가 전역 변수를 참조하고 변경할 수 있는 암묵적 결합 허용
  • 변수의 유효 범위가 크면 클수록 코드의 가독성은 나빠지고 의도치 않게 상태가 변경될 수 있는 위험성도 높아짐
  1. 긴 생명 주기
  • 전역 변수는 생명 주기가 긺 → 메모리 리소스도 오랜 기간 소비함, 전역 변수의 상태를 변경할 수 있는 시간도 길고 기회도 많음
  • var 키워드는 변수의 중복 선언을 허용하므로 생명 주기가 긴 전역 변수는 변수 이름이 중복될 가능성 존재
  1. 스코프 체인 상에서 종점에 존재
  • 전역 변수는 스코프 체인 상에서 종점에 존재 → 변수를 검색할 때 전역 변수가 가장 마지막에 검색됨 = 전역 변수의 검색 속도가 가장 느림
  1. 네임스페이스 오염
  • 다른 파일 내에서 동일한 이름으로 명명된 전역 변수나 전역 함수가 같은 스코프 내에 존재할 경우 예상치 못한 결과 유발

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

❗️ 전역 변수를 반드시 사용해야할 이유를 차지 못한다면 지역 변수를 사용하기! 변수의 스코프는 좁을수록 좋음

14.3.1 즉시 실행 함수

(function () {
  var foo = 10;
})();

console.log(foo); // ReferenceError: foo is not defined
  • 함수 정의와 동시에 호출되는 즉시 실행 함수는 단 한 번만 호출됨
  • 모든 코드를 즉시 실행 함수로 감싸면 모든 변수는 즉시 실행 함수의 지역 변수가 됨

14.3.2 네임스페이스 객체

  • 전역에 네임스페이스 역할을 담당할 객체를 생성하고 전역 변수처럼 사용하고 싶은 변수를 프로퍼티로 추가하는 방법
    var MYAPP = {}; // 전역 네임스페이스 객체
    MYAPP.name = "Lee";
    console.log(MYAPP.name);
  • 네임스페이스 객체에 또 다른 네임스페이스 객체를 프로퍼티로 추가해서 네임스페이스를 계층적으로 구성 가능
    var MYAPP = {}; // 전역 네임스페이스 객체
    MYAPP.person = {
      name: "Lee",
      address: "Seoul",
    };
    console.log(MYAPP.person.name);

14.3.3 모듈 패턴

  • 클래스를 모방해서 관련이 있는 변수와 함수를 모아 즉시 실행 함수로 감싸 하나의 모듈로 만듦
  • 모듈 패턴은 클로저를 기반으로 동작
  • 전역 변수의 억제 뿐만 아니라 캡슐화까지 구현 가능
  • JS에서는 public, private, protected 등의 접근 제한자 제공 X
    • public 멤버: 반환되는 객체의 프로퍼티

    • private 멤버: 외부로 노출하고 싶지 않은 변수나 함수는 반환하는 객체에 추가하지 X

      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.increase()); // 2
      console.log(Counter.decrease()); // 1
      console.log(Counter.decrease()); // 0

14.3.4 ES6 모듈

  • ES6 모듈은 파일 자체의 독자적인 모듈 스코프 제공
  • 모듈 내에서 var 키워드로 선언한 변수는 더는 전역 변수가 아니며 window 객체의 프로퍼티도 아님
  • 아래의 어트리뷰트 추가하면 로드된 JS 파일은 모듈로서 동작
    <script type="module" scr="lib.mjs"></script>
    <script type="module" scr="app.mjs"></script>

0개의 댓글