프론트엔드

자바스크립트 모듈 패턴(JavaScript Module Pattern: in-depth)

CyberI 2016. 10. 28. 19:06






다음 글은 Ben Cherry님이 쓴 자바스크립트 관련 게시글을 번역한 것입니다.

확실히 동의를 구하기 위해 

https://twitter.com/bcherry

를 통해 메시지를 남겼지만 답장이 없네요..

문제가 된다면 바로 삭제하도록 하겠습니다.

Cherry! we think you are so generous


원문 글입니다.

http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html



<자바스크립트 모듈 패턴에 대한 깊은 연구>


자바스크립트를 코딩하면서 모듈패턴을 만드는 것은 흔한 일입니다.

모듈 패턴을 만들면 이해하기 쉽고 많은 장점이 있습니다.

저는 모듈 패턴에 대한 관심이 많이 있는데 제가 한번 다뤄보겠습니다.


- The Basics (기초 지식)

Eric Miraglia(of YUI)로 유명한 모듈 패턴에 대해 간단히 살펴보겠습니다.

만약 모듈패턴에 이미 익숙하다면 다음으로 넘어가셔도 좋습니다.


1. Anonymous Closures (익명 클로저)

익명 클로저는 응용할 곳이 많은 자바스크립트의 주요 특징입니다.

간단히 익명함수를 만들어서 즉시 실행 시켜보겠습니다.

모든 코드는 함수 내에서 동작하며 생명주기는 클로저 안에서 돌고 있습니다.

클로저는 정보 은닉을 해주고 상태는 어플리케이션의 생명주기와 함께합니다.

(function () {

// ... all vars and functions are in this scope only

// still maintains access to all globals

}());


()안에 익명함수가 있습니다. 자바스크립트는 함수가 사용되는 시점에서 선언이 이루어집니다.

()는 함수표현을 대신합니다.


2. 전역 (Global Import)

자바스크립트는 암묵적으로 전역변수로 취급됩니다.

그리고 변수를 선언할때는 'var'라는 문자가 중요한데 var 선언을 찾아가서 scope(범위)를 형성합니다.

만약 찾아지지 않는다면, 전역변수로 간주됩니다.

즉, 따로 신경써주지 않는다면 쉽게 익명 클로저가 된다는 것입니다.

이로 인해 코드를 유지보수 하는 것이 힘들어지며 사람이 인식하기에

명확하지 않은 변수들이 생겨나게 됩니다. (전역 변수 공간이 어지럽혀진다는 것)

다행히도 대안이 있습니다. 전역변수를 파라미터로 익명함수에 주는 것입니다.

그리고 개발자는 파라미터를 전역변수 삼아 쓰는 것입니다.

이 방법이 전역변수를 선언하는 것보다 더 깔끔하고 빠릅니다.


예를 들어

(function ($, YAHOO) {

// now have access to globals jQuery (as $) and YAHOO in this code

}(jQuery, YAHOO));


3. Module Export(모듈 추출)

원치 않게 전역 변수를 설정하게 되는 경우가 있습니다. 그럴 경우 익명함수의 리턴값으로쉽게 추출할 수 있습니다. 

위 사항들을 정리한 코드는 이렇습니다.


var MODULE = (function () {

var my = {},

privateVariable = 1;

function privateMethod() {

// ...

}

my.moduleProperty = 1;

my.moduleMethod = function () {

// ...

};

return my;

}());


- Advanced Patterns (심화 학습)


위의 내용으로도 대부분 충분하지만 확장구조를 가지고 이 패턴을 더 빠르고 강력하게 만들수 있습니다.

MODULE이라는 변수를 선언해서 차근차근 살펴봅시다.


1. Augmentation (확장)

큰 규모의 코드작업은 주로 여러 파일을 쪼개서 이루어집니다.

그에 있어서 모듈패턴의 제약이라고 할만한 것이 있는데

전체 모듈이 한 파일에 담겨 있어야 한다는 것입니다.

다행히 모듈을 확장시켜갈 좋은 방법이 있습니다. 

먼저, 모듈을 import하고 특성을 추가하고 추출합니다. 샘플 코드는 이렇습니다.


var MODULE = (function (my) {

my.anotherMethod = function () {

// added method...

};

return my;

}(MODULE));


우리는 필수적인 요소는 아니지만 'var' 키워드를 지속적으로 사용할 것입니다.

이 코드가 작동한 이후 모듈은 'MODULE.anotherMethod'라는 새로운 public method를 얻게 될 것입니다.

이러한 확장 파일은 private 상태를 유지하게 됩니다.



2. Loose Augmentation (느슨한 확장)


위의 샘플에서 필수적이지는 않지만 모듈이 첫번째로 만들어지고 뒤이어 두번째로 함수의 확장이 일어납니다.

자바스크립트 어플리케이션의 좋은 점은 비동기 스크립트를 사용한다는 것입니다.

유연한 다중모듈을 만들 수 있고, 이 모듈들은 스스로 점점 확장합니다 (Loose Augmentation)

각각의 파일들은 아래의 구조를 따를 것입니다.

var MODULE = (function (my) {

// add capabilities...


return my;

}(MODULE || {}));


이 패턴에서 'var'는 꼭 필요합니다. 불러온 데이터는 (import) 기존 모듈이 존재하지 않는다면 새로 만들어 냅니다.

LABjs 같은 툴을 사용 할 수도 있으며 block이 필요 없이 당신의 모듈 파일에 상응하는 것들을 불러옵니다.


3. Tight Augmentation (강한 확장)


느슨한 확장에 비해 강한 확장은 제약이 좀 있습니다. 

가장 중요한 것은 모듈 특성을 override 할수 없다는 것입니다.

초기에 설정한 다른 파일에서 가져온 속성을 사용할 수도 없으며 (초기 설정 이후 것은 가능)

강한 확장은 로딩 순서가 정해져 있지만 overrides 됩니다.

예제가 있습니다. (우리가 위에서 만들었던 MODULE)


var MODULE = (function (my) {

var old_moduleMethod = my.moduleMethod;


my.moduleMethod = function () {

// method override, has access to old through old_moduleMethod...

};


return my;

}(MODULE));

MODULE.moduleMethod를 오버라이드 했지만 필요하다면 원래 method의 참조를 유지해야합니다.



4. Cloning and Inheritance (상속 복사)


var MODULE_TWO = (function (old) {

var my = {},

key;

for (key in old) {

if (old.hasOwnProperty(key)) {

my[key] = old[key];

}

}

var super_moduleMethod = old.moduleMethod;

my.moduleMethod = function () {

// override method on the clone, access to super through super_moduleMethod

};

return my;

}(MODULE));


이 패턴은 아마 최소한의 유동적인 옵션일 것입니다. 구성요소들이 잘 정돈되어 졌지만 그에 따른 비용적 측면이 있습니다.

object나 함수는 복제되지 못한다는 것과 참조는 두개지만 한 객채로 존재할 것이라는 것입니다.

서로 영향을 주면서 변화하게 됩니다. recursive cloning process(재귀용법)으로 고정시킬수 있지만

eval을 사용해야 그나마도 할수 있고 eval은 사용을 피해야하는 용법입니다.




5. Cross-File Private State

하나의 모듈을 여러 파일로 나누는 것의 심각한 단점은 각각의 파일이 스스로의 private state를 가지고 있는 입니다.

그리고 서로의 private state에 서로가 접근할 수 없습니다.

이 단점을 해결하는 샘플 소스가 있습니다. 느슨한 확장모듈인데 서로의 private state를 관리하게 해줍니다.


var MODULE = (function (my) {

var _private = my._private = my._private || {},

_seal = my._seal = my._seal || function () {

delete my._private;

delete my._seal;

delete my._unseal;

},

_unseal = my._unseal = my._unseal || function () {

my._private = _private;

my._seal = _seal;

my._unseal = _unseal;

};


// permanent access to _private, _seal, and _unseal


return my;

}(MODULE || {}));


어느 파일도 그들의 지역 변수를 설정할수 있으며 즉각적으로 반영됩니다.

이 모듈이 완전히 로드되자마자 MODULE._seal()을 호출할 것이고 외부에서 내부로 접근하는 것을 막을 것입니다.

만약 애플리케이션 생명주기 내에 이 모듈이 다시 확장된다면 

새로운 파일이 로딩되기 전에 아무 파일, 아무 내부 메서드중 하나가 _unseal()을 호출할 수 있고

이것이 실행된 이후에 _seal()을 호출합니다.

다른 곳에서는 못봤던 이러한 상황들은 제가 오늘 작업할때도 일어났습니다.

이러한 패턴들은 매우 유용한 패턴이며 글을 쓸 가치가 있습니다.



6. Sub-modules


마지막으로 이 모듈은 매우 간단합니다. 일반 모듈을 생성하듯이 서브 모듈을 생성하는 많은 경우들이 있습니다.

MODULE.sub = (function () {

var my = {};

// ...


return my;

}());

확실히 이 패턴은 가치가 있습니다. 

서브모듈은 확장이나 private state에 있어서 일반 모듈보다 발전되어 있습니다. 




결론


최신 패턴은 다른 유용한 패턴들이 조합되어서 만들어집니다.

만약 제가 복잡한 어플리케이션 작업에 참여한다면 loose augmentation, private state, and sub-modules을

잘 섞어서 사용할 것입니다.

이 모듈 패턴은 성능적인 측면에서 좋습니다. 코드를 축약하고 빠르게 해줍니다. 

Loose augmentation(느슨한 확장)은 병행 다운로드가 쉽고 속도도 빠릅니다.

초기 설정 시간은 다른 메서드보다 느리지만 그만한 가치가 있습니다.

전역변수 설정을 정확하게 하기만 한다면 실행 성능에 있어서 단점은 없고 

서브모듈에서 지역변수 최적화를 통해 더 좋은 속도를 낼 것입니다. 



마치며, 서브모듈 샘플을 드리겠습니다.

(없으면 만들어서) 스스로, 다양하게 조상 모듈을 로드하는 서브모듈입니다.

최적화를 위해 private state를 지양해 왔지만 오히려 포함하는게 간단할 수도  있습니다.

이 코드 패턴은 완전히 로드될 전체 코드를 따릅니다.

var UTIL = (function (parent, $) {

var my = parent.ajax = parent.ajax || {};


my.get = function (url, params, callback) {

// ok, so I'm cheating a bit :)

return $.getJSON(url, params, callback);

};

// etc...

return parent;

}(UTIL || {}, jQuery));


유용하길 바라며, 코멘트를 남겨주세요. 좋은 코딩 하시기 바랍니다.