노출패턴(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));
샌드박스 패턴은 다음과 같은 네임스페이스 패턴의 단점을 해결한다.
샌드박스 패턴은 어떤 모듈이 다른 모듈과 그 모듈의 샌드박스에 영향을 미치지 않고 동작할 수 있는 환경을 제공한다.
네임스페이스 패턴에서의 전역은 객체 하나이지만, 샌박스 패턴에서의 유일한 전역은 생성자이다.
이 생성자를 통해 객체들을 생성하며, 생성자에 콜백 함수를 전달해 해당 코드를 샌드박스 내부 환경으로 격리시킬 수 있다.
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;
}
};
We also provide door-to-door Delhi Escort service. All you need to do is let us know about it before you meet with one of our escorts, and we will send her to your place or pick her up from there at a time that suits you both.