14장 전역변수의 문제점
전역변수를 사용할 이유가 없다면 지역변수를 사용해야 한다. 이 장에서는 전역변수의 문제점과 전역변수의 사용을 억제할 수 있는 방법을 정리한다.
1. 변수의 생명주기
변수는 선언으로 생성되고 할당으로 값을 갖는다. 그리고 언젠간 소멸되는 생명주기를 갖는다.
변수는 자신이 선언된 위치에서 생성되고 소멸한다.
- 지역변수
- 함수의 생명주기와 같다.(함수가 호출되면 생성되고 함수가 종료되면 소멸한다.)
- 함수가 호출된 직후에 함수 몸체의 코드가 한 줄씩 순차적으로 실행되기 이전에 자바스크립트 엔진에 의해 먼저 실행된다.
- 함수 생명주기와 대부분 일치하나 지역변수가 더 오래 생존하는 경우도 있다.
- 전역변수
- 애플리케이션의 생명주기와 같으며 전역 객체의 생명주기와 일치한다.
- 선언문이 어디에 있든 상관없이 런타임 이전 단계에 자바스크립트 엔진에 의해 먼저 실행된다. (호이스팅)
- 함수와 달리 명시적인 호출없이 코드가 로드되자마자 곧바로 해석되고 실행된다.
- var 키워드로 선언한 전역변수는 전역 객체의 프로퍼티가 된다.
-
전역 객체란?
코드가 실행되기 이전 단계에 자바스크립트 엔진에 의해 어떤 객체보다도 먼저 생성되는 객체. 브라우저에선 window, Node.js에선 global 객체를 의미한다. 환경에 따라 전역 객체를 가리키는 식별자가 다양했으나 ES11부터
globalThis
로 통일됐다.전역객체는 표준 빌트인 객체(Object, String, Number, Function, Array…) 와 환경에 따른 호스트 객체( Web API 또는 호스트API), 그리고 var 키워드로 선언한 전역 변수와 전역 함수를 프로퍼티로 갖는다.
-
할당된 메모리 공간은 아무도 참조하지 않을 때, 가비지 콜렉터에 의해 메모리가 해제된다. 이는 누군가 메모리 공간을 참조하고 있으면 해제되지 않고 확보된 상태로 남아있는 것이다.
스코프도 마찬가지로 참조되고 있으면 생존하고 있다. ⇒ 24장 클로저.
예제 14-02
var x = 'global';
function foo(){
console.log(x); // ?
var x= 'local';
}
foo();
console.log(x); // global
- 호이스팅은 스코프 단위로 동작한다. 즉, foo() 함수 내에서 지역 변수 x는 함수 내부에서 호이스팅되어 undefined를 출력한다.
2. 전역 변수의 문제점
-
암묵적 결합(implicit coupling)
- 모든 코드가 전역 변수를 참조하고 변경할 수 있다는 점.
- 변수의 유효범위가 클수록 코드의 가독성은 나빠지고 관리가 어렵다.
-
긴 생명주기
- 생명주기가 애플리케이션 종료 때까지라 메모리 리소스도 오래 소비한다.
- 또 var키워드의 경우 변수의 중복 선언을 허용하여 변수 이름의 중복으로 의도치 않은 재할당 가능성이 있다.
-
스코프 체인 상에서 종점에 존재
- 스코프 체인 상 가장 상위에 존재하기 때문에 검색 속도가 가장 느리다.
-
네임스페이스 오염
- 자바스크립트의 문제점 중 하나로, 파일이 분리되어 있어도 하나의 전역 스코프를 공유한다.
- 다른 파일에서 동일한 이름으로 명명된 전역 변수나 전역 함수가 같은 스코프 내에 존재할 경우 예상치 못한 결과를 가져온다.
예시
//File1.js var config = { language: "English", theme: "dark" }; //File2.js var config = { language: "Korean" }; //html console.log(config.language); // "Korean" console.log(config.theme); // undefined
만약 html 문서에 File1.js와 File2.js를 차례로 불러오면 File1.js의 config 객체는 File2.js config 객체로 덮어씌워진다.
3. 전역 변수의 사용을 억제하는 법
전역 변수를 써야 할 이유가 없다면 지역변수를 사용해야 한다. 변수의 스코프는 좁을 수록 좋다.
-
즉시 실행 함수 (IIFE)
- 함수 정의와 동시에 호출되는 즉시 실행 함수는 단 한 번만 호출된다.
- 모든 코드를 즉시 실행 함수로 감싸면 모든 변수는 즉시 실행 함수의 지역변수가 된다.
- 이 방법은 전역변수를 생성하지 않으므로 다음과 같은 때 유용하게 사용된다.
- 초기화 코드 실행: 웹 페이지가 로드될 때 단 한 번만 실행되어야 하는 코드를 구성할 때
- 네임스페이스 오염 방지: 여러 스크립트 파일이나 라이브러리를 사용할 때 전역 네임스페이스를 더럽히지 않으면서 코드를 구성할 때
(function (){ var foo= 10; //즉시 실행 함수의 지역변수 }()); console.log(foo) // RefrenceError: foo is not defined
-
네임스페이스 객체
- 전역에 네임스페이스를 담당할 객체를 생성하고 전역 변수처럼 사용하고 싶은 변수를 프로퍼티로 추가한다.
var NAMESPACE={} NAMESPACE.name='Lee'; NAMESPACE.person={ name: 'lee', addr:'Seoul' }
- 네임스페이스 객체에 또 다른 네임스페이스 객체를 프로퍼티로 추가해서 계층적으로 구성할수도 있다.
- 식별자 충돌은 방지할 수 있으나 여전히 객체 자체가 전역변수에 할당되어 유용하진 않다.
-
모듈패턴
- 클래스를 모방해서 관련이 있는 변수와 함수를 모아 즉시 실행 함수로 감싸 하나의 모듈을 만든다.
- 클로저를 기반으로 동작하며, 전역 변수를 억제하면서도 캡슐화까지 구현한다.
- 캡슐화는 객체의 상태를 나타내는 프로퍼티와 프로퍼티를 참조, 조작하는 메서드를 하나로 묶는 것이다.
- 대부분 객체지향에서 제공하는 접근제한자 (private, public, protected)를 자바스크립트에서 제공하지 않는다. 이는 정보은닉의 역할을 하며, 모듈 패턴이 이 역할을 대신한다.
var Counter = (function() { //private 변수 var num =0; //외부로 공개할 데이터나 메서드를 프로퍼티로 추가한 객체를 반환 return { increase(){ return ++num; }, decrease(){ return --num; } } }()); console.log(Counter.num); //undefined console.log(Counter.increase()); //1 console.log(Counter.increase());//2 console.log(Counter.decrease());//1
//동일한 동작을 하는 자바 코드 public class Counter { private static int num = 0; // private 변수 public static int increase() { return ++num; } public static int decrease() { return --num; } } public class Main { public static void main(String[] args) { // Counter.num 접근 불가 // System.out.println(Counter.num); // 오류 발생 System.out.println(Counter.increase()); // 1 System.out.println(Counter.increase()); // 2 System.out.println(Counter.decrease()); // 1 } }
-
ES6 모듈
- 자바스크립트의 코드를 작고 관리하기 쉬운 단위로 나누는 방법으로 각 모듈은 자신만의 독립적인 스코프를 가진다.
- 모듈 안 정의된 변수나 함수는 다른 모듈과 격리되어있다. 따라서 다른 모듈과 전역 스코프와는 독립적이다.
- 모듈 내에 var 키워드로 선언한 변수는 전역변수가 아니며 window 객체의 프로퍼티도 아니다.
- script 태그에
type=”module”
어트리뷰트를 추가하면 로드된 자바스크립트 파일은 모듈로서 동작한다. - 브라우저의 ES6모듈 기능을 사용하더라도 구 브라우저에서도 동작하도록 코드 형식을 변환하는(트랜스파일링) 작업이나 모든 모듈을 하나로 합치는 번들링이 필요하여 아직까지는 브라우저가 지원하는 ES6 모듈 기능보다는 웹팩 등의 모듈 번들러를 사용한다.