자바스크립트 완벽 가이드/ 모듈과 네임스페이스

자바스크립트는 언어 자체적으로 모듈을 생성하거나 관리하기 위한 기법을 제공하지 않기 때문에, 이식할 수 있으며 재사용할 수 있는 자바스크립트 코드를 작성하는 것은 몇 가지 기본적인 관습과 관련된 문제다.

가장 중요한 관습은 두 모듈이 같은 이름을 가진 전역 프로퍼티를 정의했을 때 발생할 수 있는 이름 충돌을 피하기 위해서 네임스페이스를 사용하는 것이다. 이름 충돌이 발생하면 한 모듈이 다른 모듈의 프로퍼티를 덮어쓰고, 둘 중 한 모듈 또는 두 모듈 모두가 비정상적으로 작동한다.

다른 관습은 모듈 초기화 코드를 사용하는 것이다. 웹 브라우저의 문서를 조작하는 모듈은 때때로 문서과 완전히 로딩된 후에야 실행되는 코드를 필요로 하기 때문에, 이 관습은 특히 클라이언트 측 자바스크립트에서 중요하다.

모듈과 네임스페이스 생성

만일 여러분이 아무 스크립트에서나 사용될 수 있고 어떤 모듈과도 함께 사용될 수 있는 자바스크립트 모듈을 작성하고 싶다면, 여러분이 따라야할 가장 중요한 규칙은 바로 전역 변수를 정의하지 않는 것이다. 전역 변수를 정의하면, 여러분이 작성한 모듈을 사용하는 프로그래머 혹은 다른 모듈이 그 변수를 덮어쓸 위험이 항상 존재한다. 이를 대신할 수 있는 해결책은 여러분의 모듈에서 사용할 모든 메서드와 프로퍼티를 그 모듈을 위해 특별히 생성한 네임스페이스 안에 정의하는 것이다.

자바스크립트는 네임스페이스를 언어적으로 특별히 지원하지는 않지만, 자바스크립트 객체는 이런 용도로도 잘 작동한다. 만약 여러분이 자바스크립트 클래스와 함께 작동하는 함수 모듈을 생성하려 한다면, 이 메서드들을 전역 네임스페이스에 정의하지 말고 대신 아래와 같이 작성하라.

// 네임스페이스로서 빈 객체를 생성한다. 
// 이 단일 전역 심벌은 다른 심벌들을 가지고 있게 된다. 
var Class = { }; 
// 네임스페이스 안에서 함수를 정의한다. 
Class.define = function(data) { /* 코드는 여기에 */ } 
Class.provides = function(o, c) { /* 코드는 여기에 */ }

여기서는 자바스크립트의 인스턴스 메서드를 정의하는 것이 아니라는데 주목하라. 여러분은 일반적인 함수를 정의하고 있으며 이들에 대한 참조를 전역 객체 대신 특별히 생성된 객체에 저장한다.

자바스크립트에서 클래스라는 존재는 매우 중요한 위치를 차지하기 때문에 클래스에 대해 작동하는 모듈이 보통 하나 이상 존재한다. 만약 두 모듈이 모두 각자의 네임스페이스를 참조하기 위해서 전역 심벌인 Class를 사용한다면 네임스페이스 충돌과 함께 문제의 출발점으로 다시 돌아간다.

만약 스크립트가 하위 디렉터리에 저장되어 있다면, 하위 디렉터리의 이름은 대개 모듈 이름의 일부가 되어야 한다. 즉 정의된 ‘Class’ 모듈은 실제로는 flanagan.Class로 불려야 한다. 다음은 코드가 어떤 형태로 작성될 수 있는지 보여준다.

// flanaga/Class.js: 클래스와 함께 작동하기 위한 유틸리티 함수의 모듈 
// 이 모듈은 flanagan이라는 전역 심벌이 존재하지 않는다면 이 심벌을 생성한다. 
// 그 후, 네임스페이스 객체를 생성하고 이를 flanagan 객체의 Class 프로퍼티에 저장한다. 
// 모든 유틸리티 함수는 flanaga.Class라는 네임스페이스에 위치한다.

var flanagan; // 'flanagan' 이라는 전역 심벌을 하나 정의한다. 
if (!flanagna) flanagan = { }; // 만약 이 심벌이 정의되지 않았다면 객체를 생성한다. 
flanagan.Class = { } // 이제 네임스페이스 flanagan.Class를 생성한다.


// 이제 네임스페이스에 우리의 유틸리티 메서드를 집어 넣는다. 
flanagan.Class.define = function(data) { /* 코드는 여기에 */ }; 
flanagan.Class.provides = function(o, c) { /* 코드는 여기에 */ };

여기서 flanagan은 네임스페이스를 위한 네임스페이스다. 만약 날짜에 대해 작동하는 유틸리티 함수를 위한 모듈을 작성했다면, 이 유틸리티는 flanagan.Date라는 네임스페이스에 저장할 수 있다. 이 코드가 전역 심벌인 flanagan의 존재 여부를 검사하기 전에 먼저 var 문을 사용하여 이 심벌을 선언부터 하는데 주목하라. 이렇게 하는 이유는 선언은 되었지만 undefined 상태인 심벌을 읽으려고 하면 단순히 undefined 값을 반환하는 반면, 선언되지 않은 전역 심벌을 읽으려 하면 예외를 발생시키기 때문이다. 이는 전역 객체만의 독특한 동작이다.

두 단계로 이루어진 네임스페이스를 통해 이제 이름이 충돌하는 문제에서 안전해진 것 같지만 만약 성이 같은 사람인 다른 자바스크립트 개발자가 클래스와 관련된 유틸리티의 모듈을 작성하기로 했다면, 두 개의 모듈을 모두 사용하길 원하는 프로그래머는 문제에 봉착하지만 이런 일은 자주 발생하지 않을 것 같다. 그러나 확실히 해두기 위해서, 여러분은 전역적으로 고유한 패키지 이름의 접두사를 정하기 위한 자바 프로그램 언어의 관습을 사용할 수 있다. 이 관습에서 사용되는 접두사는 여러분만의 인터넷 도메인 이름으로 시작한다. 최상위 단계의 도메인이 가장 앞으로 올 수 있게 도메인 이름을 뒤집고 이를 여러분의 모든 자바스크립트 모듈의 접두사로 사용하라. 저자의 웹사이트는 davidflanagan.com에 있기 때문에 저자의 모듈은 com/davidflanagan/Class.jr 라는 파일에 저장되며 com.davidflanagan.Class라는 네임스페이스를 사용한다.

모듈 사용 가능성 검사

만약 여러분이 외부 모듈에 의존하는 코드를 작성하고 있다면, 단순히 그 모듈의 네임스페이스를 검사함으로써 해당 모듈이 존재하는지 테스트할 수 있다. 이 작업을 위한 코드는 네임스페이스의 각 구성요소를 검사해야 하기 때문에 조금 까다롭다. 이 코드는 com이라는 전역 심벌의 존재 여부를 검사하기 전에 먼저 이 심벌을 선언한다는 점에 주목하라.

var com; // 전역 심벌이 조재하는지 검사하기 전에 선언부터 한다. 
if ( !com || !com.davidflanagan || !com.davidflanagan.Class) 
throw new Error("com/davidflanaga/Class.js has not been loaded");

모듈로 사용되는 클래스

앞선 예의 ‘Class’ 모듈은 단순히 서로 협력하여 작동하는 유틸리티 함수들의 집합이다. 하지만 여기에는 모듈이 무엇이 되어야 하는지에 대한 제약은 없다. 즉 모듈이 함수 한 개로 구성될 수 있으며 자바스크립트 클래스나 협력 클래스들과 함수들의 집합이 될 수도 있다.

여러분은 두 개 이상의 클래스로 이루어진 모듈도 정의할 수 있다.

모듈 초기화 코드

자바스크립트 모듈은 함수의 모음으로 간주되는 경향이 있다. 하지만 앞의 예에서 확실히 알 수 있었던 바와 같이 모듈은 나중에 호출될 함수를 정의하는 것 이상의 일을 한다. 모듈은 네임스페이스를 설정하고 할당하는 시점에도 코드를 실행할 수 있다. 모듈은 이런 종류의 일회용 코드가 아무리 많아도 실행시킬 수 있다. 또한 함수나 클래스를 전혀 정의하지 않고 단지 어떤 코드만 실행시키는 모듈을 작성할 수도 있다. 유일한 규칙은 전역 네임스페이스를 어지럽히면 안 된다는 것이다. 이 규칙을 지키는 모듈을 구성하는 가장 좋은 방법은 다음과 같이 정의된 직후 호출되는 익명의 함수 속에 코드를 넣는 것이다.

( function( ) { // 익명의 함수를 정의한다. 이름이 없다는 것은 전역 심벌이 없음을 의미한다. 
    // 코드는 여기에 놓인다. 
    // 어떤 변수라도 함수 안에 안전하게 둘러 쌓여있기 때문에, 전역 심벌은 생성되지 않는다. 
} ) ( ); // 함수 정의를 끝내고 이 함수를 호출한다.

어떤 모듈은 로딩이 완료 됐을 때 자신만의 초기화 코드를 실행시킬 수 있다. 그 외 다른 모듈에는 나중에 호출될 초기화 함수가 있어야 한다. 이는 클라이언트측 자바스크립트에서는 보편화되었다. HTML 문서에 대해 작동하도록 만들어진 모듈들은 보통 HTML 문서가 웹 브라우저 상에 로딩되는 것이 끝났을 때 실행되는 초기화 코드가 필요하다.

단순히 초기화 함수를 정의하고 이 함수가 초기화 함수라고 문서에 적어두어 모듈 사용자가 초기화 함수를 적당한 시점에 호출하게 하는 방법이 있다. 이 방법에서는 모듈 초기화 문제에 대해 수동적인 자세를 취한다. 이렇게 하는 것이 안전한 동시에 조심성 있는 접근법이긴 하지만, 이 방법을 사용하더라도 HTML 문서 내에는 적어도 이 문서에 대해 작동할 모듈을 초기화하기 위한 자바스크립트 코드 정도는 존재해야 한다.

네임스페이스에서 심벌 가져오기

com.davidflanagan.Class 같은 고유한 네임스페이스의 문제점은 함수의 이름을 너무 길게 만든다는 것이다. 자바스크립트 함수는 데이터이기 때문에 여러분이 원하는 곳에 이들을 할당할 수 있다. 예를 들어, com.davidflanagan.Class 모듈을 로딩한 후 모듈 사용자는 다음과 같이 쓸 수 있다.

// 이것은 짧게 입력하기 위한 좀 더 쉬운 이름이다. 
var define = com.davidflanagan.Class.define;

이름 충돌을 방지하기 위해서 네임스페이스를 사용하는 것은 모듈 개발자의 책임이다. 하지만 심벌을 모듈의 네임스페이스에서 전역 네임스페이스로 가져오는 것은 모듈 사용자의 특권이다.

위의 코드는 define이 어떤 것인지 말해주지 않기 때문에 전역 함수를 위해서 좋은 선택이 아니다. 때문에 아래와 같은 식으로 심벌을 가져올 수 있다.

// 간단한 네임스페이스를 생성한다. 에러 검사는 필요없다. 
// 모듈 사용자는 이 심벌이 아직 존재하지 않는다는 사실을 알고 있다.
var Class = { };

// 이제 심벌을 새로운 네임스페이스로 가져온다.
Class.define = com.davidflanagan.Class.define;

이러한 형태로 심벌을 가져오는 것을 이해하기 위한 몇 가지 중요한 점이 있다. 첫째, 여러분은 함수나 객체 또는 배열을 참조하는 심벌들만 가져올 수 있다. 만약 값이 숫자나 문자열 같은 기본 타입인 심벌을 가져온다면, 단순히 그 값의 정적인 사본을 얻는다. 값에 대한 변화는 네임스페이스 안에서만 발생하며 가져온 사본에는 반영되지 않는다. 둘째, 가져오기가 모듈 사용자를 위한 것임을 이해하라. 모듈 개발자는 항상 완벽한 전체 이름을 사용해야 한다. 자바스크립트는 모듈과 네임스페이스를 위한 지원이 없기 때문에 이를 처리하기 위한 쉬운 방법이 없으며, 접근 메서드가 모두 같은 네임스페이스의 일부라도 프로퍼티의 이름을 완벽히 기술해야 한다. 모듈 작성자는 자신들이 작성한 함수가 전역 네임스페이스로 들어가게 될 것이라고 가정해서는 안된다. 모듈 안에 있는 다른 함수를 호출하는 함수는 이 함수를 가져오지 않고 호출해도 제대로 작동할 수 있게 완벽한 전체 함수 이름이 사용되어야 한다.

public과 private 심벌

모듈의 네임스페이스 내에 정의된 모든 심벌이 외부에서 사용하기 위한 것은 아니다. 모듈은 자신을 직접 사용하는 스크립트가 아니라 자신이 내부적으로 사용하기 위한 함수와 변수를 가질 수 있다. 자바스크립트에는 네임스페이스 내의 어떤 프로퍼티가 public인지 아닌지를 기술할 방법이 없기 때문에, 모듈의 private 프로퍼티가 외부에서 부적절하게 사용되는 것을 막기 위해 기존 관습을 사용한다.

가장 직관적인 방법은 문서를 간단히 만드는 것이다. 문서화 작업을 하지 않고서 public/ private을 구별하는 관습은 private 인 심벌의 접두사로 밑줄을 사용하는 것이다. 여러분은 counter의 프로퍼티가 private 라는 점을 명확하게 하기 위해 이름을 _counter로 바꿀 수 있다.

private 네임스페이스와 유효 범위로서의 클로저

클로저는 함수일 뿐만 아니라 함수가 정의되었을 때 효력이 미치는 유효 범위라고 한 것을 떠올려 보라. 따라서 함수 정의를 통해 여러분은 함수의 지역적 유효 범위를 private 네임스페이스로 사용할 수 있다. 바깥 함수에 둘러싸인 함수는 이 private 네임스페이스에 접근할 수 있다. 이 기법에는 두 가지 면에서 장점이 있다. 우선, private 네임스페이스는 유효 범위 체인 상에서 첫 번째 객체이기 때문에 네인스페이스 안의 함수는, 같은 네임스페이스 안에 있는 다른 함수와 프로퍼티를 완벽한 전체 이름 없이 참조할 수 있다.

private 네임스페이스를 정의하기 위해 함수를 사용해서 얻을 수 있는 두 번째 장점은 이것이 진정한 private이라는 점이다. 함수 안에서 정의된 심벌들은 그 함수 외부에서 접근할 수 있는 방법이 없다. 이 심벌들은 함수가 이들을 외부의 public 네임스페이스로 보낼 때만 사용할 수 있다. 이것이 의미하는 바는 모듈이 자신의 public 함수만 외부에 드러내고, 도움 메서드와 변수는 클로저의 개인적인 영역에 놔둘 수 있다는 것을 의미한다.

It's only fair to share...Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

The author

지성을 추구하는 디자이너/ suyeongpark@abyne.com

댓글 남기기