스태틱 멤버



// 생성자
var Gadget = (function(){

// 스태틱 변수/프로퍼티
var counter = 0,
NewGadget;
// 이 부분이 생성자를 새롭게 구현한 부분
NewGadget = function(){
console.log(counter += 1);
};

// 특권 메서드
NewGadget.prototype.getLastId = function(){
return counter;
};

// 생성자를 덮어쓴다.
return NewGadget;
}());

var iphone = new Gadget();
iphone.getLastId(); // 1
var ipod = new Gadget();
ipod.getLastId(); // 2
var ipad = new Gadget();
ipad.getLastId(); // 3


전역 생성자


네임스페이스 패턴에서는 전역 객체가 하나다. 샌드박스 패턴의 유일한 전역은 생성자다.
// 샌드박스 사용법은 다음과 같다.
new Sandbox(function (box){
// 여기에 코드가 들어감
});

다음과 같이 new를 쓰지 않고도 가상의 모듈 'ajax'와 'event'를 사용하는 객체를 만들 수 있다.
Sandbox(['ajax', 'event'], function(box){
//console.log(box);
})

// 개별적인 인자로 전달 할 수 있다
Sandbox('ajax','dom', function(box){
//console.log(box);
})

샌드박스 객체의 인스턴스를 여러 개 만드는 예제
Sandbox(['dom', 'events'], function(box) {
// box 객체는 dom, events 모듈이 결합되어 있다.

Sandbox('ajax', function(box) {
// box 객체는 ajax 모듈만 결합되어 있다.
// 이 box 객체는 바깥 쪽 box 객체와 다르다.
});
});




모듈 추가하기


실제 생성자를 구현하기 전에 모듈을 어떻게 추가할 수 있는지 확인
// Sandbox 모듈 객체
Sandbox.modules = {};

// Sandbox 모듈 dom 정의
Sandbox.modules.dom = function(box) {
box.query = function(selector, context) {};
box.queryAll = function(selector, context) {};
box.css = function(el, prop, value) {};
}

// Sandbox 모듈 events 정의
Sandbox.modules.events = function(box) {
// 필요에 따라 Sandbox 프로토타입 객체에 접근 가능
// box.constructor.prototype.prop = 'value';
box.on = function(el, type, handler, capture) {};
box.off = function(el, type, handler, capture) {};
}

// Sandbox 모듈 ajax 정의
Sandbox.modules.ajax = function(box) {
box.makeRequest = function() {};
box.getResponse = function() {};
}




생성자 구현


function Sandbox() {
// arguments를 배열로 변경한다.
var args = Array.prototype.slice.call(arguments),
// 마지막 인자는 항상 콜백 함수이다.
callback = args.pop(),
// 모듈 이름은 배열 또는 문자열로 전달될 수 있다.
modules = (args[0] && typeof args[0] === 'string') ? args : args[0],
i;

// new를 강제화하는 패턴
if ( !(this instanceof Sandbox) ) {
return new Sandbox(modules, callback);
}

// 생성된 인스턴스 객체(this)에 속성을 추가 한다.
this.prop1 = 'property 1';
this.prop2 = 'property 2';

// this 객체에 모듈을 추가한다.
// 모듈이 없거나, '*' 와일드 카드라면 모든 모듈을 사용한다.
if (!modules || modules === '*' || modules[0] === '*') {
modules = [];
for ( var module in Sandbox.modules) {
if(Sandbox.modules.hasOwnProperty(module)){
modules.push(module);
}
}
}

// 필요한 모듈을 초기화 한다.
modules.forEach(function(module, index) {
Sandbox.modules[ module ](this);
});

// 콜백 함수를 실행한다.
callback(this);
}

// Sandbox 프로토타입 객체
Sandbox.prototype = {
name: 'Application',
version: '1.0.2',
getName: function(){
return this.name;
}
// ...
};


이 구현에서 핵심적인 사항은 다음과 같다.

  • this가 Sandbox 인스턴스인지 확인 후, 생성자 함수로 호출한다. (new를 강제화하는 패턴)
  • 생성자 내부에서 this에 속성을 추가한다. 생성자의 프로토타입 객체에도 속성을 추가할 수 있다.
  • 필요한 모듈은 배열 또는 개별 문자 유형의 인자로 전달할 수 있고, * 와일드카드를 사용하거나, 쓸 수 있는 모든 모듈을 사용하겠다는 의미로 인자를 생략할 수도 있다.
  • 필요한 모듈을 모두 파악한 다음에는 각 모듈을 초기화한다. 정리하면 각 모듈을 구현한 함수를 호출해서 객체를 생성한다.
  • 생성자의 마지막 인자는 콜백 함수이다. 이 콜백 함수는 맨 마지막에 호출되며, 새로 생성된 인스턴스가 인자로 전달된다. 이 콜백 함수가 실제로 사용자의 샌드박스이며 필요한 기능을 모두 갖춘 상태에서 box 객체를 전달받게 된다.


모듈 패턴


모듈 패턴은 늘어나는 코드를 구조화하고 정리하는데 도움이 되기 때문에 널리 쓰인다.
모듈 패턴은 다음 여러 패턴 여러개를 조합한 것이다.
  • 네임 스페이스 패턴
  • 즉시 실행 함수
  • 비공개 멤버와 특권 멤버
  • 의존 관계 선언


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


// 모듈 정의
MYAPP.utilities.array = (function(){
// 의존관계
var uobj = MYAPP.utilities.object,
ulan = MYAPP.utilities.lang,
// 비공개 프로퍼티
ayyay_string = "[object Array]",
ops = Object.prototype.toString;

// 비공개 메서드

// var 선언 종료

// 필요시 일회성 초기화 실행

// 공개 API
return {
inArray: function(needle, haystack){
for(var i = 0, max = haystack.langth; i < max; i += 1){
if(haystackp[i] === needle){
return true;
}
}
},

isArray: function(a){
return ops.call(a) === array_string;
}
// 더 필요한 메서드 추가
}
}())



모듈 노출 패턴


모든 메서드를 비공개 상태로 유지하고 최종적으로 공개 API를 갖출 대 공개할 메서드만 골라서 노출 하는 것이다.

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

// 모듈 정의
MYAPP.utilities.array = (function(){
// 비공개 프로퍼티
var ayyay_string = "[object Array]",
ops = Object.prototype.toString;

// 비공개 메서드
inArray = function(needle, haystack){
for(var i = 0, max = haystack.langth; i < max; i += 1){
if(haystackp[i] === needle){
return i;
}
}
return -1;
},
isArray = function(a){
return ops.call(a) === array_string;
};
// var 선언 종료

// 공개 API
return {
inArray: inArray,
indexOd: inArray
};
}());




생성자를 생성하는 모듈


앞선 예제는 MYAPP, utilities, array 라는 객체를 만들었다. 하지만 생성자 함수를 사용해 객체를 만드는게 더 편할때도 있다.

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

// 모듈 정의
MYAPP.utilities.Array = (function(){
// 의존관계
var uobj = MYAPP.utilities.object,
ulan = MYAPP.utilities.lang,

// 비공개 메서드
inArray = function(needle, haystack){
for(var i = 0, max = haystack.langth; i < max; i += 1){
if(haystackp[i] === needle){
return I;
}
}
return -1;
},
isArray = function(a){
return ops.call(a) === array_string;
},
Constr;
// var 선언 종료

// 공개 API 생성자
Constr = function(o){
this.elements = this.toArray(o);
};
// 공개 API 프로토타입
Constr.prototype = {
constructor: MYAPP.utilities.Array,
version: "2.0",
toArray: function(obj){
for(var i = 0, a = [], len = obj.length; i < len; i += 1){
ap[i] = obj[i];
}
return a;
}
}

// 생성자 함수를 반환한다
// 이 함수가 새로운 네임스페이스에 할당 될 것이다.
return Constr;
}());

// 이 생성자 함수는 다음과 같이 사용
var arr = new MYAPP.utilities.Array(obj);



모듈에 전역 변수 가져오기


변경 패턴으로는 모듈을 감싼 즉시 실행 함수에 인자를 전달하는 형태가 있다.

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


비공개 프로퍼티와 메서드


자바 등 다른언어와는 달리 자바스크립트에는 private, protected, public 프로퍼티와 메서드를 나타내는 별도의 문법이 없다.

객체의 모든 멤버는 pulic, 즉 공개되어 있다.


var myobj = {
myprop: 1,
getProp: function(){
return this.myprop;
}
};
// 두개 모두 공개적으로 접근이 가능
console.log(myobj.myprop);
console.log(myobj.getProp());

// 생성자 함수를 사용해 객체를 생성할 때도 마찬가지로 모든 멤버가 공개된다.
function Gadget(){
this.name = 'iPod';
this.stretch = function(){
return 'iPod';
};
}
// 두개 모두 공개적으로 접근이 가능
var toy = new Gadget();
console.log(toy.name);
console.log(toy.stretch());



비공개 멤버


비공개 멤버에 대한 별도의 문법은 없지만 클로저를 사용해서 구현할 수 있다.
생성자 함수 안에서 클로저를 만들면 클로저 유효범위 안의 변수는 생성자 함수 외부에 노출되지 않지만 객체의 공개 메서드 안에서는 쓸 수 있다. 즉 생성자에서 객체를 반환할 때 객체의 메서드를 정의하면 이 메서드 안에서는 비공개 변수에 접근할 수 있는것이다.

function Gadget(){
// 비공개 멤버
var name = 'iPod';
// 공개된 함수
this.getName = function(){
return name;
};
}

var toy = new Gadget();

// name 은 비공개 이므로 undefied 출력
console.log(toy.name);

// 공개 메서드에서는 name에 접근할 수 있다.
console.log(toy.getName());



비공개 멤버의 허점


function Gadget(){
// 비공개 멤버
var specs = {
screen_width: 320,
screen_height: 480,
color: "white"
};

// 공개함수
this.getSpecs = function(){
return specs;
}
}

var toy = new Gadget();

// name 은 비공개 이므로 undefied 출력
console.log(toy.name);

// 공개 메서드에서는 name에 접근할 수 있다.
console.log(toy.getName());


getSpec() 메서드가 specs 객체에 대한 참조를 반환하는 문제가 있다.

var toy = new Gadget(),
specs = toy.getSpecs();
specs.color = "black";
specs.price = "free";

console.dir(toy.getSpecs());

/*
파이어폭스 로그 결과

color: "black"​
price: "free"​
screen_height: 480​
screen_width: 320​
<prototype>: Object { … }
*/



객체 리터럴과 비공개 멤버



// v1
var myobj; // 이 변수에 객체를 할당
(function(){
// 비공개 멤버
var name = "my, oh my";

// 공개될 부분을 구현
myobj = {
getName: function(){
return name;
}
}
}());

console.log(myobj.getName());

// v2
var myobj = (function(){
// 비공개 멤버
var name = "my, oh my";

// 공개될 부분을 구현
return {
getName: function(){
return name;
}
}
}());

console.log(myobj.getName());




프로토타입과 비공개 멤버


생성자를 사용하여 비공개 멤버를 만들 경우 생성자를 호출하여 새로운 객체를 만들 때마다 비공개 멤버가 매번 재생성된다는 단점이 있다.
이러한 중복을 없애고 메모리를 절약하려면 공통 프로퍼티와 메서드를 생성자의 prototype 프로퍼티에 추가해야 한다.
function Gadget(){
// 비공개 멤버
var name = 'iPod';
// 공개함수
this.getName = function(){
return name;
};
}

Gadget.prototype = (function(){
// 비공개 멤버
var browser = "Mobile Webkit";

// 공개된 프로토타입 멤버
return {
getBrowser : function(){
return browser;
}
}
}())

var toy = new Gadget();
console.log(toy.getName()); // 객체 인스턴스의 특권 메서드
console.log(toy.getBrowser()); // 프로토타입의 특권 메서드



비공개 함수를 공개 메서드로 노출 시키는 방법


노출 패턴은 비공개 메서드를 구현하면서 동시에 공개 메서드도 노출 하는것을 말한다.
var myarray;

(function(){
var astr = "[object Array]",
toString = Object.prototype.toString;

function isArray(a){
return toString.call(a) === astr;
}

function indexOf(haystack, needle){
var i = 0,
max = haystack.length;
for (; i < max; i += 1){
if(haystack[i] === needle){
return i;
}
}
return -1;
}

myarray = {
isArray: isArray,
indexOf: indexOf,
inArray: indexOf
};
}());

console.log(myarray.isArray([1,2])); // TRUE
var a = myarray.isArray({0: 1}); // FALSE
// console.log(a); // false
console.log(myarray.indexOf(["A","B","C"], "C")); // 2
console.log(myarray.inArray(["A","B","C"], "C")); // 2


네임스페이스 패턴


네임스페이스는 프로그램에서 필요로 하는 전역 변수의 개수를 줄이는 동시에 과도한 접두어를 사용하지 않고도 이름이 겹치지 않게 해준다.
어플리케이션이나 라이브러리를 위한 전역 객체를 하나 만들고 모든 기능을 이 객체에 추가하면 된다.

// 수정전 : 전역변수 5개
// 경고 : 안티패턴

// 생성자 함수 2개
function Parent(){}
function Child(){}

// 변수 1개
var some_var = 1;

// 객체 2개
var modulel = {};
modulel.data = {a:1, b:2};
var modulel2 = {};



// 수정 후 : 전역 변수 1개

// 전역 객체
var MYAPP = {};

// 생성자
MYAPP.Parent = function(){};
MYAPP.Child = function(){};

// 변수
MYAPP.some_var = 1;

// 객체 컨테이너
MYAPP.modules = {};

// 객체들을 컨테이너 안에 추가한다.
MYAPP.modules.modules1 = {};
MYAPP.modules.modules1.data = {a:1, b:2};
MYAPP.modules.modules2 = {};


이 패턴은 코드에 네임스페이스를 지정해주며, 코드 내의 이름 충돌 뿐 아니라 이 코드와 같은 페이지에 존재하는 자바스크립트 라이브러리나 위짓 등 서드 파티 코드와의 이름 충돌도 방지해준다.



범용 네임스페이스 함수


네임 스페이스를 생성하거나 프로퍼티를 추가하기 전에 먼저 이미 존재하는지 여부를 확인 하는 것이 최선이다.

// 위험
var MYAPP = {};
// 개선안
if (typeof MYAPP === "undefined"){
var MYAPP = {};
}
// 또는 더 짧게 쓸 수 있따.
var MYAPP = MYAPP || {};


이렇게 추가되는 확인 작업 때문에 상당량의 중복 코드가 생겨날 수 있다. 따라서 네임스페이스 생성의 실제 작업을 맡아 줄 재사용 가능한 함수를 만들어 두면 편하다.

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;
};


이 코드는 다음 모든 예에서 사용할 수 있다.

// 반환 값을 지역 변수에 할당한다.
var module2 = MYAPP.namespace('MYAPP.modules.module2');
module2 === MYAPP.modules.module2; // true

// 첫부분의 'MYAPP' 을 생략하고도 쓸 수 있다.
MYAPP.namespace('modules.module51');

// 아주 긴 네임스페이스
MYAPP.namespace('once.ipon.a.time.there.was.long.nested.property');


for-in 루프

for-in 루프는 배열이 아닌 객체를 순회할 때만 사용해야 한다. for-in 으로 루프를 도는것을 열거 라고도 한다.


// 객체
var man = {
hands: 2,
legs: 2,
heads: 1
};

// 코드 어딘가에서 모든 객체에 메서드 하나가 추가 되었다.
if(typeof Object.prototype.clone === 'undefined'){
Object.prototype.clone = function(){};
};


이 예제에서는 객체 리터럴을 사용하여 man 이라는 이름의 간단한 객체를 정의했다. man을 정의하기 전이나 후 어디선가 Object 프로토타입에 clone()이라는 이름의 메서드가 편의상 추가 되었다.


man을 열거할때 hasOwnProperty()를 호출하여 걸러내지 않으면 clone()이 나오게 되므로 동작방식이 바람직 하지 않다.


// 객체
var man = {
hands: 2,
legs: 2,
heads: 1
};

// 코드 어딘가에서 모든 객체에 메서드 하나가 추가 되었다.
if(typeof Object.prototype.clone === 'undefined'){
Object.prototype.clone = function(){};
};

//1.
// for-in 루프
for(var i in man){
if(man.hasOwnProperty(i)){ // 프로토타입 프로퍼티를 걸러낸다.
console.log(i + " : " + man[i]);
}
}

/*
콘솔에 출력되는 결과
hands : 2
legs : 2
heads : 1
*/

// 2.
// 안티패턴 :
// hasOwnProperty()를 확인하지 않는 for-in 루프
for(var i in man){
console.log(i + " : " + man[i]);
}

/*
콘솔에 출력되는 결과
hands : 2
legs : 2
heads : 1
clone : function(){}
*/


Object.property에서 hasOwnProperty()를 호출하는 것도 하나의 패턴이다.


for (i in man){
if(Object.prototype.hasOwnProperty.call(man, i)){ // 걸러내기
console.log(i + " : " + man[i])
}
}


이 방법은 man 객체가 hasOwnProperty를 재정의하여 덮어썼을 경우에도 활용 할 수 있다는 장점이 있다. 프로퍼티 탐색이 Object까지 거슬러 올라가지 않게 하려면 지역변수를 사용하여 이 메서드를 캐시하면된다.


var i,
hasOwn = Object.prototype.hasOwnProperty;
for(i in man){
if (hasOwn.call(man, i)){ // 걸러내기
console.log(i + " : " + man[i]);
}
}


for 루프

 

최적화 되지 않은 루프

이 패턴의 문제점은 루프 순회시 마다 배열의 length에 접근한다는 점이다.


for(var i=0; i<myarray.length; i++){ 
	// myarray[i] 를 다루는 코드 
}

 

for 루프를 좀더 최적화 하기 위해서는 다음 예제처럼 배열의 length를 캐시해야 한다.


for(var i=0, max = myarray.length; i<max; i++){
	// myarray[i] 를 다루는 코드
}

이렇게 하면 length 값을 한번만 구하고 루프를 도는 동안 이 값을 사용하게 된다.

 

단일 var 패턴을 따르자면, var 문을 루프 외부로 빼내어 다음과 같이 만들수 있다.


function looper() { 
	var i=0, max myarray = []; 
    // ... 
    for(i=0, max = myarray.length; i<max; i++){
    // myarray[i] 를 다루는 코드
    } 
}

이 패턴은 단일 var 패턴을 고수하여 일관성을지킨다는 장점이 있다.

 

i++ 명령문을 다음중 하나로 대체할 수 있으며, JSLint는 이 방법을 권장한다.

i = i+1

i += 1

 

for 문에는 두 가지 변형 패턴이 있다.

이 패턴들은 다음과 같은 미세 최적화를 시도 한다.

 

첫번째 변형 패턴


var i, myarray = [];

for (i = myarray.length; i--){ 
	// myarray[i]를 다루는 코드 
}

두번째 변형 패턴은 whlie 루프를 사용한다.


var myarray = [],i = myarray.length;
    
while (i--){
	// myarray[i] 를 다루는 코드
}

    + Recent posts