자바스크립트 모듈패턴이란? (3)

이정민 Lee Jeong Min·2021년 2월 28일
0

Javascript

목록 보기
8/10

노출패턴

노출패턴(revelation pattern)은 private 메서드를 구현하는 동시에 public 메서드로도 노출시키는 것이다.
메서드가 public하다는 것은 이 메서드가 위험에 노출되어 있다는 말과도 같다. es5에서부터는 객체를 고정시키는 선택자, freeze가 나왔지만 이전까진 그렇지 않다.

여기에서 작성된 예시 코드를 노출패턴을 적용시켜 작성해보자.

var MYAPP = MYAPP || {};

MYAPP.namespace = function (ns_string) {
    var parts  = ns_string.split('.'),
        parent = MYAPP,
        i;

	if (parts[0] === 'MYAPP') {
      parts = parts.slice(1);
	}

	for (i = 0; i < parts.length; i += 1) {
		if (typeof parent[parts[i]] === 'undefined') {
			parent[parts[i]] = {};
		}
		parent = parent[parts[i]];
	}
	
	return parent;

};

MYAPP.namespace('MYAPP.utilities.array');

MYAPP.utilities.array = (function () {
	
    var arr_string = '[object Array]',
        ops        = Object.prototype.toString;
    
    var inArray = function (haystack, needle) {
	    for (var i = 0, max = haystack.length; i < max; i += 1) {
		    if (haystack[i] === needle) {
		        return i;
		    }
	    }
	    return -1;
    };
    
    var isArray = function (a) {
        return ops.call(a) === arr_string;
    };
    
    return {
        isArray : isArray,
	    inArray : inArray
    }
    
}());

객체 리터럴 안에서 private 멤버를 만들 때, public하게 해도 괜찮겠다고 결정한 기능들을 반환시키는 것을 볼 수 있다.

MYAPP.utilities.array.isArray([1, 2]); // true
MYAPP.utilities.array.isArray({0: 1}); // false
MYAPP.utilities.array.indexOf(['a', 'b', 's'], 's'); // 2
MYAPP.utilities.array.inArray(['a', 'b', 's'], 's'); // 2

MYAPP.utilities.array.indexOf = null;
MYAPP.utilities.array.indexOf(['a', 'b', 's'], 's'); // TypeError: arr.indexOf is not a function
MYAPP.utilities.array.inArray(['a', 'b', 's'], 's'); // 2

public한 indexOf에 예기치 못한 일이 발생하더라도, private한 indexOf는 보호되기 때문에 inArray는 계속 잘 동작하는 것을 볼 수 있다.

생성자를 생성하는 모듈

위의 예시에서는 MYAPP.utilities.array 객체를 직접 만들었지만, 생성자 함수로 객체를 만드는 것이 더 편할 때도 있다.
이 경우, 모듈을 감싼 즉시실행함수가 객체가 아닌 함수를 반환하게 하면 된다.

MYAPP.namespace('MYAPP.utilities.Array');

MYAPP.utilities.Array = (function(){
	
	// 의존 관계 선언
	var uobj  = MYAPP.utilities.object,
	    ulang = MYAPP.utilities.lang;
	
   	// private 속성
	var Constr;
	
    // 필요하다면 일회성 초기화 코드 삽입
	
	// public - 생성자 함수
	Constr = function (o) {
		this.elements = this.toArray(o);
	};
	
	// public - 프로토타입
	Constr.prototype = {
		consturctor : MYAPP.utilities.Array,
		version : '2.0',
		toArray : function (obj) {
			for (var i = 0, a = [], len = obj.length; i < len; i += 1) {
				a[i] = obj[i];
			}
			return a;
		}
	};
	
	// 생성자 함수를 반환하면 이 함수는 새로운 네임스페이스에 할당됨
	return Constr;
	
}());

var arr = new MYAPP.utilities.Array({});
console.log(arr.version); // 2.0

모듈에 전역변수 가져오기

모듈을 감싼 즉시실행함수에 인자를 전달할 수도 있는데, 어떠한 값이라도 가능하지만 보통 전역 변수에 대한 참조 또는 전역 객체 자체를 전달한다.

이렇게 전역 변수를 전달하면 즉시 실행 함수 내에서 지역 변수로 사용할 수 있게 되기 때문에 탐색 작업이 좀더 빨리진다.

MYAPP.utilities.module = (function (app, global) {
	
	// 전역 객체에 대한 참조와 전역 애플리케이션 네임스페이스 객체에 대한 참조가 지역 변수화됨
	
}(MYAPP, this));

샌드박스 패턴

샌드박스 패턴은 다음과 같은 네임스페이스 패턴의 단점을 해결한다.

  • 애플리케이션 전역 객체가 단 하나의 전역 변수에 의존한다.
    따라서 네임스페이스 패턴으로는 동일한 애플리케이션이나 라이브러리의 두 가지 버전을 한 페이지에서 실행시키는 것이 불가능하다. 여러 버전들이 모두 동일한 전역 변수명을 쓰기 때문이다.
  • comma로 연결된 긴 이름을 써야 하고 런타임에 탐색 작업을 거쳐야 한다.

샌드박스 패턴은 어떤 모듈이 다른 모듈과 그 모듈의 샌드박스에 영향을 미치지 않고 동작할 수 있는 환경을 제공한다.

전역 생성자

네임스페이스 패턴에서의 전역은 객체 하나이지만, 샌박스 패턴에서의 유일한 전역은 생성자이다.
이 생성자를 통해 객체들을 생성하며, 생성자에 콜백 함수를 전달해 해당 코드를 샌드박스 내부 환경으로 격리시킬 수 있다.

new Sandbox(function (box) {})

네임스페이스 패턴에서의 MYAPP과 같은 기능을 하는 것이 box 객체이다.

new를 강제하는 패턴을 통해 객체 생성시 new를 안 써도 되게 할 수 있다.

Sandbox(['ajax','event'], function (box) {})

생성자가 선택적인 인자를 하나 이상 받을 수 있게 하여 모듈 이름을 개별적인 인자로 전달할 수 있다.

Sandbox('ajax','dom', function (box) {})

'쓸 수 있는 모든 모듈을 사용한다'는 의미로 *를 사용하고,
모듈명을 누락시키면 샌드박스가 자동으로 *를 가정하도록 하자,

Sandbox('*', function (box) {});

Sandbox(function (box) {});

샌드박스 객체의 인스턴스를 여러 개 만들 때,
한 인스턴스 내부에 다른 인스턴스를 중첩시킬 수도 있다.
이 때 두 인스턴스 간의 간섭 현상을 일어나지 않는다.

Sandbox('dom', 'event', function (box) {
	
	// dom 과 event 를 가지고 작업하는 코드
	Sandbox('ajax', function () {
		// 샌드박스된 box 객체를 또 하나 만듦
		// 이 "box" 객체는 바깥쪽 함수의 "box" 객체와는 다름
		
		// ajax 작업
		
	});
	
	// 더 이상 ajax 모듈의 흔적은 찾아볼 수 없음
  
});

샌드박스 패턴을 사용하면 콜백 함수로 코드를 감싸기 때문에 전역 네임스페이스를 보호할 수 있다.
필요하다면 '함수 또한 객체'라는 점을 활용하여 Sandbox() 생성자의 static 속성에 데이터를 저장할 수도 있다.
원하는 유형별로 모듈의 인스턴스를 여러 개 만들 수도 있으며, 이 인스턴스들은 각각 독립적으로 동작한다.

모듈 추가하기

Sandbox() 생성자 함수 또한 객체이므로 modules 속성을 추가할 수 있다.
각 모듈을 구현하는 함수들이 현재의 인스턴스 box를 인자로 받아들인 다음, box에 속성과 메서드를 추가하게 된다.

Sandbox.modules = {};

Sandbox.modules.dom = function (box) {
	box.getElement = function () {
	};
	box.getStyle = function () {
	};
	box.foo = 'bar';
};

Sandbox.modules.event = function (box) {
	// 필요에 따라 다음과 같이 Sandbox 프로토타입에 접근할 수 있다.
	// box.constructor.prototype.m = 'mmm';
	
	box.attachEvent = function () {
	};
	box.detachEvent = function () {
	};
	
};

Sandbox.modules.ajax = function (box) {
	box.makeRequest = function () {
	};
	box.getResponse = function () {
	};
};

생성자 구현하기

function Sandbox() {
	// arguments를 배열로 변경
	var args = Array.prototype.slice.call(arguments);
	
	// 마지막 인자는 콜백함수
	var callback = args.pop();
	
	// 모듈은 배열로 전달될 수도 있고, 개별 인자로 전달될 수도 있음
	var modules = (args[0] && typeof args[0] === 'string') ? args : args[0];
	var i;
	
	// 함수가 생성자로 호출되도록 보장(new 를 강제하는 패턴)
	if ((!this instanceof Sandbox)) {
		return new Sandbox(modules, callback);
	}
	
	// this 에 필요한 프로퍼티 추가
	this.a = 1;
	this.b = 2;
	
	// 코어 'this' 객체에 모듈을 추가
	// 모듈이 없거나 '*' 이면 사용 가능한 모든 모듈을 사용한다는 의미
	if (!modules || modules === '*' || modules[0] === '*') {
		modules = [];
		for (i in Sandbox.modules) {
			if (Sandbox.modules.hasOwnProperty(i)) {
				modules.push(i);
			}
		}
	}
	
	// 필요한 모듈들 초기화
	for (i = 0; i < modules.length; i += 1) {
		Sandbox.modules[modules[i]](this);
	}
	
	// 콜백 함수 호출
	callback(this);
	
}

// 필요한 프로토타입 속성들 추가
Sandbox.prototype = {
	name: 'My Application',
	version : '1.0',
	getName : function () {
		return this.name;
	}
};
  • this 가 Sandbox 의 인스턴스인지 확인하고, 그렇지 않으면 (Sandbox() 가 new 없이 호출되었다면) 함수를 생성자로 호출한다.
  • 생성자 내부에서 this에 속성을 추가한다. 생성자의 프로토타입에도 속성을 추가할 수 있다.
  • 필요한 모듈은 배열로도 또는 개별적인 인자로도 전달할 수 있고, *를 사용할 수도 있고, 생략할 수도 있다.
  • 필요한 모듈을 모두 파악한 다음에는 각 모듈을 초기화한다. (각 모듈을 구현한 함수 호출)
  • 생성자의 마지막 인자는 콜백 함수이다. 이 콜백 함수는 맨 마지막에 호출되며, 새로 생성된 인스턴스가 인자로 전달된다. 이 콜백 함수가 실제 사용자의 샌드박스이며 필요한 기능을 모두 갖춘 상태에서 box 객체를 전달받게 된다.

Reference

profile
https://jeong-min.com/ <- 블로그 이전했습니다 :)

0개의 댓글