탱구탱구 개발자 일기

[전역 유효 범위 오염]

전역 유효 범위를 오염시킨다는 말은 전역 변수 전역 함수 전역 객체에 선언하는 행위를 말한다. 전역 유효 범위가 오염되면 변수 이름과 함수 이름이 겹치는 상황이 발생한다. 다음과 같은 상황에서 많이 발생한다.

 

  • 라이브러리 파일을 여러 개 읽어 들여 사용할 때
  • 규모가 큰 프로그램을 만들 때
  • 여러 사람이 한 프로그램을 만들 때

전역 유효 범위 안에서 이름이 같은 변수, 함수를 선언하면 자바스크립트 엔진은 호이스팅을 할 때 동일한 이름 중 하나만 생성한다. 따라서 해당 변수나 함수를 사용하는 다른 프로그램에서 오작동이 발생할 수 있다. 심지어 오류가 발생하지 않기 때문에 디버깅이 쉽지 않다.

 

예를 들어 한 페이지에서 a라는 모듈과 b라는 자바스크립트 모듈을 동시에 불러올 때 해당 모듈에 동일한 이름의 전역 변수가 선언되어 있다면?? 아래 예제를 살펴보자

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script type="text/javascript" src="./test1.js"></script>
<script type="text/javascript" src="./test2.js"></script>
<body>
    <h1>전역변수 중복</h1>
</body>
</html>
// test1.js
var global = 10;

// test2.js
var global = 20;

이렇게 var 키워드를 통해 global이라는 동일한 변수가 선언되었고 이는 최상위 레벨에서 선언되었기 때문에 전역 변수이다. 과연 해당 페이지에서 global을 참조할 때 어떤 값을 불러올까?? 바로20이다. 이것은 유효 범위 체인에 따라서 해당 변수 global의 식별자 결정이 된다. 자바스크립트의 식별자 결정 규칙은 좀 더 안쪽 코드에 선언된 변수를 사용한다는 것이다.

 

window.global;
// 20

 

전역 유효 범위의 오염을 최소하는 것이 좋은 개발 방식이다. 그렇다면 어떻게 할 수 있을까?? 바로 객체와 함수를 통해 네임스페이스를 만들고 참조 값을 할당하는 방식을 사용했다.

 

[네임스페이스 패턴]

 

네임스페이스(이름 공간, Namespace)란 이름이 존재하는 공간이다. 즉, 이름들을 한곳에 모아 충돌을 미리 방지하고 해당 이름으로 선언된 변수와 함수를 쉽게 가져다 쓸 수 있도록 만든 메커니즘이다. 네임스페이스 패턴 애플리케이션 or 라이브러리를 위한 하나의 전역객체를 생성그 안에 필요한 모든 기능을 프로퍼티로 정의하는 것이다.

  • Java : 패키지
  • C++ : 네임스페이스
  • Javascript : 따로 기능이 없는 대신, 객체 리터럴즉시 실행 함수를 통해 그 기능을 대신함

[객체를 네임스페이스로 활용]

 

객체의 기초편에서 객체를 만드는 방식 중 하나로 객체 리터럴이 있었는데 이 객체 리터럴은 프로퍼티로 일반 값 뿐만아니라 함수(메서드)도 가능하기 때문이다.

// 객체 리터럴
var app = app || {};

// 프로퍼티 추가
app.name = "준영";
app.age = 30;
app.gender = "남";
app.nameCard = function(){
    console.log('nameCard:', this); // 여기서 this는 app임
    return `${this.age}세 ${this.name}`;
};

위와 같이 논리합 연산자를 사용하면 app이 기존에 정의되어 있을 대는 그것을 사용하고 그렇지 않으면 빈 객체를 app에 할당한다. 이렇게 하고 전역 유효 범위에서 사용하고자 하는 모든 변수와 함수를 이 객체의 프로퍼티로 추가하면 프로그램에서 app이라는 하나의 변수만 전역 객체 프로퍼티로 등록하여 해당 객체에서 필요한 모든 기능을 참조해 사용할 수 있다. 또한 이 객체 안에 또다른 부분 네임스페이스를 생성할 수 있다.

// 부분 네임스페이스 생성
app.subApp = {};
app.subApp.tempDate = new Date();
app.subApp.temp = function () {
    console.log('temp:', this); // subApp을 가리킴
    return `${app.name}의 입사일은 ${this.tempDate}입니다.`
}

부분 네임스페이스에선 root 프로퍼티가 또 다른 공간의 root가 되기 때문에 계층적으로 관리할 수 있다.

 

[함수를 네임스페이스로 활용]

 

자바스크립트의 함수 레벨 스코프에 따라 함수 안에서 선언된 변수의 유효 범위는 함수 내부이다. 오호라~ 그러면 함수 내부에 프로그램 작성하면 되겠구나?? 라고 생각할 수 있을 것이다. 바로 즉시 실행 함수를 활용한다면 해당 즉시 실행 함수 안에서 선언된 모든 것은 해당 함수의 지역 변수이므로 전역 변수이므로 라이브러리를 불러오고 해당 라이브러리 변수들이 전역으로 등록되어도 프로그램 내부에서 사용한 변수들과 전혀 겹치지 않는다. 따라서, 전역 공간을 전혀 건들지 않는다. 

// 즉시 실행 함수

(function () {
 // 이곳에 프로그램을 작성하자
})();

[모듈 패턴]

 

즉시 실행 함수를 사용하면 이처럼 전역 변수를 전혀 사용하지 않을 수 있지만 정작, 즉시 실행 함수에 정의된 변수나 기능이 필요한 경우엔 바깥에선 참조할 수 없기 때문에 사용할 수가 없다. 그렇다면 어떻게 해야할까?? 아래 코드에서 확인해보자.

 

  1. 전역변수 Module은 Module이 있다면 기존 Module을 없다면 빈 객체를 참조값으로 받아 선언되고 초기화된다.
  2. 없다고 가정한다면 즉시 실행 함수의 파라미터( _Module )는 빈 객체이기 때문에 즉, 프로퍼티가 없는 깨끗한 네임스페이스가 만들어진다.
  3. 외부에 공개할 변수나 함수는 해당 네임스페이스의 프로퍼티로 추가한다.
  4. 공개하지 않을 변수나, 함수들은 프로퍼티로 추가하지 않는다.
  5. 그리고 해당 이름 공간은 변수 Module에 할당되어 전역 변수로 사용가능하다.
var Module = Module || {};

(function (_Module) {
    var name = "noName"; // 비공개

    function getName() { // 비공개
        return name;
    }

    _Module.showName = function () { // 공개
        console.log(getName());
    };
    _Module.setName = function (x) { // 공개
        name = x;
    };

})(Module);

 이런 것을 클로저라고 하는데 전형적인 모듈 패턴의 예다. 클로저는 다음 포스팅에서 살펴보도록 하자.

 

[ECMAScript 6 이후 let, const의 등장]

 

결론부터 말하자면 사실상 ECMAScript6 이후 let, const 선언 키워드가 등장하면서 이와 같은 문제를 상당 부분 해소됐다고 말할 수 있다고 조심스럽게 생각하고 있다. 왜냐하면 전역 환경(최상위 레벨)에서 let이나 const로 선언한 변수나 함수는 각각 전역 변수, 전역 함수가 맞지만 전역 객체에는 등록되지 않을 뿐더러 동일한 레벨 스코프에서 재선언을 허용하지 않기 때문이다. 자세한 사항은 이 글을 참고하자.

// let, const 전역 변수 선언
let global = 10;
window.hasOwnProperty("global"); // false

const global2 = 20;
window.hasOwnProperty("global2"); // false

let global = 10; // Uncaught SyntaxError: Identifier 'global' has already been declared

이럼 변수 선언할 때는 전역 객체에 등록되지 않고 동일한 이름의 변수를 재선언할 수 없다.

이 글을 공유합시다

facebook twitter kakaoTalk kakaostory naver band
loading