네임스페이스 패턴


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

// 수정전 : 전역변수 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');


커리(Curry)


순수한 함수형 프로그래밍 언어에서 함수는 불려지거나 호출된다고 표현하기 보다 적용된다고 표현한다.
다음은 함수 적용의 예다
** call은 인자를 하나하나씩 전달하지만 apply는 배열로 인자를 전달한다.


// 함수를 정의한다.
var sayHi = function(who){
return "hello" + (who ? ", " + who : "") + "!";
// who가 true일 경우에 "," false일 경우에 ""
};

// 함수를 호출한다.
console.log(sayHi()); // hello
console.log(sayHi('world')); // hello, world!

// 함수를 적용(apply) 한다.
console.log(sayHi.apply(null, ['hello'])); // 'hello, hello!'


예제에서 보는것처럼 함수를 적용하는 것과 호출하는 것 모두 결과는 동일하다. apply()는 두개의 매개변수를 받는다. 첫번째는 이 함수 내에 this와 바인딩할 객체이고, 두번째는 배열 또는 인자(arguments)로 함수 내부에서 배열과 비슷한 형태의 arguments 객체로 사용하게 된다. 첫번째 매개변수가 null이면 this는 전역객체를 가리킨다. 즉 함수를 특정 객체의 메서드로서가 아니라 일반적인 함수로 호출할 때와 같다.


함수가 객체의 메서드일때는 null을 전달하지 않는다


var sayHi = function(who){
return "hello" + (who ? ", " + who : "") + "!";
// who가 true일 경우에 "," false일 경우에 ""
};

var alien = {
sayHi: function(who){
return 'hello' + (who ? ', ' + who : '') + '!';
}
};

console.log(alien.sayHi('world')); // 'hello, world!'
console.log(sayHi.apply(alien, ['humans'])); // 'hello, humans!'


이 코드에서 sayHi() 내부의 this는 alien을 가리킨다. 앞선 예제에서 this는 전역 객체를 가리킨다.


apply()와 더불어 Function.prototype 객체에 call() 메서드도 있다는 것을 알아둬여한다. call() 메서드 역시 apply()와 매우 비슷하다.

함수의 매개변수가 단 하나일때는 굳이 배열을 만들지 않고 요소 하나만 지정하는 방법이 더 편하기 때문에 call()을 쓰는게 더 나을 때도 있다.


var sayHi = function(who){
return "hello" + (who ? ", " + who : "") + "!";
// who가 true일 경우에 "," false일 경우에 ""
};

var alien = {
sayHi: function(who){
return 'hello' + (who ? ', ' + who : '') + '!';
}
};

// 배열을 만들지 않는 두 번째 방법이 더 효과적
console.log(sayHi.apply(alien, ['humans'])); // 'hello, humans!'
console.log(sayHi.call(alien, 'humans')); // 'hello, humans!'



부분적인 적용


인자 전부가 아니라 일부 인자만 전달하는 방법에 대해 설명한다.
다음 코드는 가상의 partialApply() 메서드 사용법을 보여준다.

var add = function(x, y){
return x + y;
};

// 모든 인자를 적용한다.
console.log(add.apply(null, [5, 4])); // 9
// 인자를 부분적으로만 적용한다.
var newadd = add.partialApply(null, [5]);
// 새로운 함수에 인자를 적용
newadd.apply(null, [4]); // 9



커링(Curring)


커링은 함수를 변형하는 과정이다. 자바스크립트에서는 add() 함수를 수정하여 부분 적용을 처리하는 커링 함수로 만들 수 있다.

// 커링된 add()
// 부분적인 인자의 목록을 받는다.
function add(x, y){
if(typeof y === 'undefined'){ // 부분적인 적용
return function(y){
return x + y;
};
}
// 전체 인자를 적용
return x + y;
}

// 테스트
console.log(typeof add(5)); // function
console.log(add(3)(4));

// 새로운 함수를 만들어 저장
var add2000 = add(2000);
console.log(add2000(10)); // 2010


다음 예제는 이를 수행하는 범용 함수를 보여준다.

function schonfinkelize(fn){
var slice = Array.prototype.slice,
stored_args = slice.call(arguments, 1);
return function(){
var new_args = slice.call(arguments),
args = stored_args.concat(new_args);
return fn.apply(null, args);
}
}


- Array.prototype 에서 slice()메서드를 빌려와서 arguments를 배열로 바꿔 사용함
- 지역변수 slice에 slize()메서드에 대한 참조를 저장하고 stored_args에 인자를 저장함
- 이때 첫번째 인자는 커링될 함수이기 때문에 떼어냄
- 새로 반환된 함수는 클로저를 통해 비공개로 저장해둔 stored_args와 slice 참조에 접근
- 새로운 함수는 이미 일부 적용된 인자인 stored_args와 새로운 인자 new_args를 합친뒤 클로저에 저장되어 있는 fn에 적용

테스트

// 일반함수
function add(x, y){
return x + y;
}

// 함수를 커링하여 새로운 함수를 얻는다.
var newadd = schonfinkelize(add, 5);
console.log(newadd(4)); // 9

console.log(schonfinkelize(add, 6)(7)); // 13

function add2(a, b, c, d, e){
return a + b + c + d + e;
}

// 여러개의 인자를 사용할 수도 있다.
console.log(schonfinkelize(add2, 1, 2, 3)(5, 5)); // 16

// 2단계 커링
var addOne = schonfinkelize(add2, 1);
console.log(addOne(10, 10, 10, 10)); // 41
var addSix = schonfinkelize(addOne, 2, 3);
console.log(addSix(5, 5)); // 16



커링을 사용해야 할 경우


어떤 함수를 호출할 때 대부분의 매개변수가 항상 비슷하다면, 커링의 적합한 후보라고 할 수 있다.


설정 객체 패턴


설정 객체 패턴은 좀더 깨끗한 API를 제공하는 방법이다. 라이브러리나 다른 프로그램에서 사용할 코드를 만들때 특히 유용하다.
많은 수의 매개변수를 전달하기 불편할 경우 하나의 객체로 만들어 대신 전달하는 방법이 있다.

function addPerson(){ // 혹은 addPerson(obj)
// console.log(obj)
console.log(arguments);
if(arguments[0]){
console.log(arguments[0].username);
}
}

var conf = {
username : "batman",
first : "bruce",
lst : "wayne"
};

addPerson(conf);


설정 객체의 장점은 다음과 같다.

  • 매개변수와 순서를 기억할 필요가 없다.
  • 선택적인 매개변수를 안전하게 생략할 수 있다.
  • 읽기 쉽고 유지보수가 편하다.
  • 매개변수를 추가하거나 제거하기가 편하다.
설정객체의 단점은 다음과 같다.
  • 배개변수의 이름을 기억해야 한다.
  • 프로퍼티 이름은 압축되지 않는다.
이 패턴은 함수가 dom 앨리먼트를 생성할 때나 엘리먼트의 css 스타일을 지정할 때 유용하다. 엘리먼트와 스타일은 많은 수의 어트리뷰트와 프로퍼티를 가지며 대부분은 선택적은 값이기 때문이다.


+ Recent posts