모듈 (Modules)
여러분이 만든 자바스크립트를 사용하는 어플리케이션들중 얼마나 많은 것들이 아래처럼 파일에 삽입되어 있나요? (HTML 파일이라고 말하지 않은것에 주목. HTML이라면 언급할 가치도 없습니다...)
var someSharedValue = 10;
var myFunction = function (){ //do something }
var anotherImportantFunction = function () { //do more stuff }
|
|
이 글을 읽는 시점에, 여러분이 다루는(혹은 만드는) 코드는 대부분 위처럼 되어있을겁니다. 여러분을 비난하려는건 아닙니다. 저도 오래도록 이렇게 코딩했었으니 까요. 이런 코드의 문제점은 여러가지가 있습니다만 여기서는 전역 네임스페이스를 지저분하게 만들어 버린다는 것에 집중해 봅시다(역자주: 전역 네임스페이스는 소스코드내의 모든 영역에서 참조가 가능하기 때문에, 여러 소스를 함께 로드할 경우 전역 변수가 같은 이름을 가지면 소스코드가 맛이 갑니다.. 가요). 이렇게 코드를 짜면 모든 메서드와 변수가 전역 네임스페이스에 들어가기 때문에 얘네를 전역 변수에서 걷어낼 필요가 있는데, 여기에 사용되는것이 '모듈 패턴' 입니다. 모듈은 여러가지 모습으로 만들수 있지만 쉬우면서도 요새 유행하는 방법이 IIFE(Immediately Invoked Founction Expression: 선언과 동시에 실행되는 함수 표현) 입니다.
이름은 길고도 거창하지만, 코드로 써보면 간단합니다.
( function (){
//do some work
})();
|
|
IIFE를 이전에 써본적이 없는 분들은, '뭐 이렇게 생겨먹은 코드가 다있나...' 하실 겁니다. 이 코드에는 괄호가 많이 나와 혼란스러울수 있는데, 기본적으로는 익명함수(anonymous function)와 그 뒤에 () 로 함수를 실행하는 부분으로 이루어져 있습니다. 즉 함수 선언 + 실행이 한번에 이루어 지는것이죠. 그래서 "Immediately invoke function"가 IIFE라는 이름에 포함되어있는것이고, 나머지 "expression" 부분은 이 코드속의 함수가 익명함수이므로 statement가 아닌 expression에 해당됨을 의미합니다. (역자주: statement 와 expression은 여러가지 차이점이 있는데, 여기서는 expression이 모여서 statement 가 되고, 세미콜론으로 한 문장을 완성해야 statement 가 된다 정도로 알고 넘어가면 될듯 합니다. 또한 함수가 statement 가 되려면 이름을 가져야 합니다). 이 과정은 익명함수 겉에 ()로 감싸주는 부분때문에 가능합니다. 또한 이런 표현 (함수 선언 밖에 ()로 감싸는 것) 때문에 IIFE 방식이 코드에 사용되었다는것을 쉽게 알아볼 수 있습니다.
이제 구현방법을 알았으니, 이걸 왜 쓰는지도 알아봅시다. 자바스크립트에서는 scope를 사용하기 위해서는 함수를 써야 합니다 (역자주: scope는 변수의 접근 제한 단위이며, 자바스크립트는 함수 단위로 scope를 구분합니다). IIFE방식으로 실행된 익명함수는 그 자체로 scope를 제한하게 됩니다. 따라서 전역 네임스페이스를 건드리지 않게 되지요. 다만, 익명함수 안에서 사용하는 모든 변수의 scope가 그 함수 내부로만 제한되어서, 함수 밖에서 접근하려면 전역 네임스페이스와의 연결고리를 만들어주어야 합니다.
이를 위한 가장 간단한 방법중에 하나는 window 오브젝트를 함수에 파라미터로 넘겨주고, 그 함수 안에서 변수나 메서드를 window 오브젝트에 연결 해줘서 함수 외부에서도 실행이 가능하게 하는것입니다. (역자주: window 오브젝트는 글로벌 오브젝트로써 자바스크립트에서는 최상위 오브젝트 입니다. 여기에 값이나 함수를 할당하면 당연히 전역 변수 혹은 함수가 됩니다). window 변수가 꼬이는것을 방지하기 위해, window 객체를 명시적으로 파라미터로 넘겨줍니다. 다른 라이브러리들의 레퍼런스나 심지어 'undefined' 값도 이렇게 넘겨줄 수 있습니다. 예를들면 이런 형태가 되겠군요.
( function (window, $, undefined){
//do some work
})(window, jQuery);
|
|
보시다 시피, window 와 jQuery($) 를 파라미터로 넘겨주고 있지만, 함수에는 3개의 파라미터를 받고 있습니다. 그 이유는, 내부의 익명함수를 실행할때 3번째 파라미터를 넘겨주지 않았기 때문에, 다른 모듈에서 'undefined' 라는 변수의 값을 변경해도, 적어도 이 모듈 안에서는 undefined값을 가지도록 보장하기 위함입니다. (역자주: 자바스크립트에서는 undefined가 primitive 값의 한 종류 입니다). 자바스크립트에는 클로저라는 개념이 있어서, 함수가 정의될때 그 함수 외부의 scope를 참조할수 있도록 자동으로 매핑이 되기 때문에, 위의 경우 undefined 같은 글로벌 변수를 파라미터로 넘기지 않아도 함수 내부에서 참조가 가능합니다. 클로저라는 개념은 따로 글을 하나 써야 할만큼 길기 때문에 저자의 다른글(http://www.codethinked.com/c-closures-explained) 을 참조하기 바랍니다.
이제 여러분은 window, $, undfined 변수를 보다 안전하게 사용할수 있는 즉시 실행된 메서드를 작성 할수 있게 되었습니다. (물론 이 메서드가 실행되기전에 이런 변수들 값이 바뀔 위험은 여전히 있지만, 그래도 비교적 안전할것입니다). 이제 여러분은 전역 네임스페이스를 보다 깔끔하게 만들수 있고, 다른 자바스크립트 모듈과의 간섭이 생갈 가능성을 많이 줄이게 되었습니다.
남은건 window 오브젝트에 모듈을 연결 해주는것 뿐입니다. 하지만 저는 모듈을 직접 window 오브젝트에 연결하는것 보다는 체계적으로 연결이 되는 기능을 만드는것이 좋다고 생각합니다. 대부분의 컴퓨터 언어에서는 이걸 네임스페이스라 하고, 자바스크립트의 오브젝트개념을 이용하여 흉내내보겠습니다.
네임스페이스 (Namespaces)
네임스페이스를 선언하고 함수를 연결해 주기 위해서는 아마 이렇게 많이 할겁니다.
window.myApp = window.myApp || {};
window.myApp.someFunction = function (){
//so some work
};
|
|
이건 단지 이미 같은 이름으로 생성된 오브젝트가 있나 보고 없으면 {} 표현을 사용해서 새 오브젝트를 만들어 주는 정도 입니다. 하지만 이 코드 처럼 네임스페이스를 만드는 것 보다는, 위에서 작성한 모듈방식을 활용해 보죠. 이렇게 말이죠.
( function (myApp, $, undefined){
//do some work
}(window.myApp = window.myApp || {}, jQuery));
|
|
혹은 이렇게도 가능합니다.
window.myApp = ( function (myApp, $, undefined){
//do some work
return myApp;
})(window.myApp || {}, jQuery);
|
|
이제 window 오브젝트를 모듈에 넘겨주는 대신, 윈도우 오브젝트에서 파생된 네임스페이스 오브젝트를 넘겨 주게 되었습니다. || 표현을 쓴 이유는 여러 소스에서 같은 네임스페이스를 쓸 경우 새로운 오브젝트를 매번 만들지 않도록 하기 위함입니다. 많은 라이브러리들이 네임스페이스 함수를 제공하고 있으며, namespace.js(https://github.com/maximebf/Namespace.js) 를 쓰는것도 좋은 방법입니다. 또한 네임스페이스는 너무 여러 depth로 중첩되지 않도록 하는것이 좋습니다. 예를들어 "doSometing"이라는 메서드를 "MyApp.MyModule.MySubModule"라는 네임스페이스 하위에 추가한다면, 이 메서드를 쓰기위해선 아래처럼 길게 써야겠죠
MyApp.MyModule.MySubModule.doSomething(); |
|
매번 이렇게 사용하기 힘들며 아래처럼 변수에 붙여서 쓸수는 있습니다.
var MySubModule = MyApp.MyModule.MySubModule; |
|
이렇게 하면 "MySubModule.doSomething()" 정도로 호출이 가능하죠. 하지만 왠만큼 대규모의 코드를 짜는게 아니라면 이렇게 여러번 중첩된 네임스페이스를 안쓰는게 좋습니다.
노출식 모듈 패턴 (Revealing Module Pattern)
모듈을 만들다 보면 노출식 모듈 패턴이라 불리는 아래 방식을 자주 보게 됩니다. 이 패턴은 모듈 생성 방식의 하나로써, 모듈 내부에서 모든 값들을 처리한 다음, 외부에 공개하고 싶은 변수와 메서드에 대해서만 레퍼런스 오브젝트를 리턴 해주는 것입니다. 아래 코드를 보면 어떻게 사용되는지 알 수 있을 겁니다.
var myModule = ( function ($, undefined){
var myVar1 = '' ,
myVar2 = '' ;
var someFunction = function (){
return myVar1 + " " + myVar2;
};
return {
getMyVar1: function () { return myVar1; }, //myVar1 public getter
setMyVar1: function (val) { myVar1 = val; }, //myVar1 public setter
someFunction: someFunction //some function made public
}
})(jQuery);
|
|
보시다시피, 모듈 생성이 간단하게 한번에 이루어 집니다. 그리고 이 모듈 생성 방식으로 인해 private 변수는 내부에 숨겨지고, 외부에 노출하고 싶은 일부 내용만 오브젝트 형식으로 리턴되고 있습니다. 결국 "myModule"라는 변수에 대입되어, 이 변수는 두개의 public 메서드를 가지게 되지만, 내부에 있는 "someFunction"은 priviate 변수인 "myVal2"같은것에 접근이 가능합니다.
생성자 만들기 (Creating Constructors: Classes)
자바스크립트에는 클래스가 없지만, 오브젝트 생성시의 생성자 함수를 통해 구현이 가능합니다. 'Person' 오브젝트를 여러개 생성한다고 해 봅시다. 생성시에는 이름과 성, 나이를 파라미터로 넘겨줄수 있도록 하겠습니다. 아마 이런 형태의 생성자가 될것입니다. (역자주: 여기서 글쓴이가 말하는 생성자가 곧 클래스를 의미합니다)
var Person = function (firstName, lastName, age){
this .firstName = firstName;
this .lastName = lastName;
this .age = age;
}
Person.prototype.fullName = function (){
return this .firstName + " " + this .lastName;
};
|
|
둘중 위쪽 함수는 'Person' 의 생성자 입니다. 이걸로 새로운 'Person' 오브젝트 생성할수 있습니다. 이때 3개의 파라미터가 넘어가는데, 생성자가 실행되면서 자연스럽게 변수에 입력이 됩니다. 이렇게 생성된 변수는 public 변수가 됩니다. 생성자 내부에 따로 로컬 변수를 생성하면 private변수로 사용이 가능하지만, public 메서드에서 접근이 불가능해지므로 일단 모두 public 변수로 만들어 둡니다. 사실 생성자 내부에 public 메서드를 만들면 그 메서드는 private변수에 접근이 가능하긴 해도 또 다른 문제가 있습니다.
그다음의 두번째 함수쪽을 보면 "prototype"을 통해 'person'오브젝트의 생성자에 접근하고 있습니다. "prototype" 오브젝트는 어떤 함수든 인스턴스가 생성되면, 그 인스턴스들에게 연결되어서 해당 함수의 변수나 메서드에 접근할때 가장 먼저 접근하는 수단이 됩니다. 따라서 여기서 "fullName" 이라는 메서드를 만들면, 'Person' 인스턴스를 만들때 따로 "fullName"이라는 메서드를 만들어 주지않아도 모든 'Person'의 인스턴스에서 사용이 가능합니다. 생성자 내부에서 "this.fullName = function() { ... }"같은 방식으로 "fullName"같은 메서드를 생성자 내부에서 만들 수도 있긴 하지만 이렇게 하면 'Person'의 인스턴스를 만들때마다 "fullName"함수도 사본이 만들어지게되어서 좋지 못한 방법입니다.
이제 'Person'의 인스턴스를 생성해 보겠습니다.
| var person = new Person( "Justin" , "Etheredge" );
alert(person.fullName());
|
|
원한다면, 'Person' 생성자에서 상속을 한 새로운 생성자를 만들수도 있습니다. 'Spy'라는 생성자를 만들고, 거기에 메서드를 하나 추가해 보겠습니다.
var Spy = function (firstName, lastName, age){
this .firstName = firstName;
this .lastName = lastName;
this .age = age;
};
Spy.prototype = new Person();
Spy.prototype.spy = function (){
alert( this .fullName() + " is spying." );
}
var mySpy = new Spy( "Mr." , "Spy" , 50);
mySpy.spy();
|
|
보시다시피 'Person'과 똑같은 모습의 생성자를 만들었습니다. 다만 'Spy'의 prototype에 'Person'의 생성자를 연결해 주었습니다. 그리고 거기에 'spy' 메서드를 추가한 다음 'Spy'의 인스턴스를 하나 생성해 주었습니다. 이 메서드는 'Spy'의 'Person'의 public 메서드에 접근이 가능하며, 'Person'의 메서드들 또한 'Spy' 인스턴스의 변수들에 접근이 가능합니다. 다소 복잡한 감이 있지만, 큰 틀에서 보면 굉장히 멋진 방법이 아닐수 없습니다.
엮어 보기 (Wrapping it up)
이 글을 여기 까지 읽은 여러분이 뭔가를 배웠다면 좋겠지만, 이걸로는 최신 자바스크립트 개발이라는 경지에 이르렀다고 보기는 어렵습니다. 여기에 소개된 주제들은 이미 몇년이상 사용된 비교적 낡은 기법들이니까요. 하지만 적어도 고수가 되기 위한 길에 한발을 내딛었다고 볼수는 있습니다. 여기서 배운 기법을 바탕으로 여러분이 만드는 코드를 모듈화 해서 여러 파일로 나눠 보십시오. 그 다음엔 자바스크립트의 결합(combination)과 경량화(minification)에 대해 공부할 차례입니다. 여러분이 만약 Rails 개발자이고 Rails3을 쓴다면 이 두가지를 쉽게 한번에 해결가능 할것입니다. 그외에 .NET 개발자라면 제가 시도해봤던 SquishIt(https://github.com/jetheredge/SquishIt)을 활용할수도 있을것입니다. 그외에 ASP.NET MVC4 를 사용한다면, 내장된 결합과 경량화 지원 기능이 있을것입니다.
끝으로 이글이 도움이 되었기를 바라며, 다음 글에서 또 다른 최신 자바스립트 개발에 관한 주제로 만날수 있기를 바랍니다.