설정 객체 패턴


설정 객체 패턴은 좀더 깨끗한 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 스타일을 지정할 때 유용하다. 엘리먼트와 스타일은 많은 수의 어트리뷰트와 프로퍼티를 가지며 대부분은 선택적은 값이기 때문이다.


함수 프로퍼티(메모이제이션)


함수는 객체이기 때문에 프로퍼티를 가질 수 있다. 함수는 처음부터 length 프로퍼티를 갖는다.

function fnc(a, b, c){}
console.log(fnc.length); // 3


다음 예제에서는 myFunc 함수에 cache 프로퍼티를 생성한다.  cache 프로퍼티는 함수로 전달된 param 매개변수를 키로 사용하고 계산의 결과를 값으로 가지는 객체다.


var myFunc = function(param){
if(!myFunc.cache[param]){
var retult = {};
// 비용이 많이 드는 수행
myFunc.cache[param] = result;
}
return myFunc.cache[param];
};

// 캐시 저장공간
myFunc.cache = {};


만약 더 많은 매개변수와 더 복잡한 타입을 갖는다면 일반적으로 직렬화하여 해결할 수 있다. 예를들어, 객체 인자를 JSON문자열로 직렬화하고 이 문자열을 cache 객체에 키로 사용할 수 있다.


var myFunc = function(){

var cachekey = JSON.stringify(
// myFync로 전달되는 argument를 받아서 array 타입으로 변환
Array.prototype.slice.call(arguments))
, retult;
if(!myFunc.cache[cachekey]){
var retult = {};
// 비용이 많이 드는 수행
myFunc.cache[cachekey] = result;
}
return myFunc.cache[cachekey];
};

// 캐시 저장공간
myFunc.cache = {};


직렬화하면 객체를 식별할 수 없게 되는것을 주의해야 한다. 만약 같은 프로퍼티를 가지는 두 개의 다른 객체를 직렬화하면 이 두객체는 같은 캐시 항목을 공유하게 될것이다.

이 함수를 작성하는 다른 방법으로 함수 이름을 하드코딩하는 대신 arguments.callee를 사용해 함수를 참조할 수 있다.

** ECMAScript 5 스트릭트 모드에서는 허용되지 않는다.


var myFunc = function(maram){

var f = arguments.callee, retult;
if(!f.cache[param]){
var retult = {};
// 비용이 많이 드는 수행
f.cache[param] = result;
}
return f.cache[param];
};

// 캐시 저장공간
myFunc.cache = {};


초기화 시점의 분기


초기화 시점의 분기는 최적화 패턴이다. 어떤 조건이 프로그램의 생명주기 동안 변경되지 않는게 확실할 경우 조건을 단 한 번만 확인하는것이 바람직하다. 브라우저 탐지가 전형적인 예다.

DOM 엘리먼트의 계산된 스타일을 확인하거나 이벤트 핸들러를 붙이는 작업도 초기화 시점 분기 패턴의 이점을 살릴 수 있는 또 다른 후보들이다.


// 인터페이스
var utils = {
addListener : null,
removeListener : null
};

// 구현
if(typeof window.addEventListener === 'function'){
utils.addListener = function(el, type, fn){
el.addEventListener(type, fn, false);
};
utils.removeListener = function(el, type, fn){
el.removeEventListener(type, fn, false);
};
} else if (typeof document.attechEvent === 'function'){ // ie
utils.addListener = function(el, type, fn){
el.attachEvent('on' + type, fn);
};
utils.removeListener = function(el, type, fn){
el.detachEvent('on' + type, fn);
};
} else { // 구형 브라우저
utile.addListener = function(el, type, fn){
el['on' + type] = fn;
};
utils.removeListener = function(el, type, fn){
eel['on' + type] = null;
};
}



즉시 객체 초기화


전역 유효범위가 난잡해지지 않도록 보호하는 또 다른 방법은 즉시 실행 함수 패턴과 비슷한 즉시 객체 초기화 패턴이다. 이 패턴은 객체가 생성된 즉시 init() 메서드를 실행해 객체를 사용한다.

({
// 여기에 설정 값(설정상수)들을 정의할 수 있다.
maxwidth: 600,
maxheight: 400,

// 유틸리티 메서드 또한 정의할 수 있다.
gimmeMax : function(){
return this.maxwidth + "x" + this.maxheight;
},

// 초기화
fnInit: function(){
console.log(this.gimmeMax());
// 더 많은 초기화 작업
}

}).fnInit(); // 초기화 함수 실행


문법적인 면에서 이 패턴은 객체 리터럴을 사용한 일반적인 객체 생성과 똑같이 생각하면 된다. 객체 리터럴도 괄호로 감싸는데 이는 자바스크립트 엔진이 중괄호를 코드블록이 아니라 객체 리터럴로 인식하도록 지시하는 역할을 한다.

객체만 괄고로 감싸는게 아니라 객체와 init() 호출 전체를 괄호 안에 넣을 수도 있다.


({
//...
}).init();

({
//...
}.init());


이 패턴의 장점은 즉시 실행 함수 패턴의 장점과 동일하다. 단 한 번의 초기화 작업을 실행하는 동안 전역 네임스페이스를 보호할 수 있다.

즉시 실행 함수


  • 즉시 실행 함수 패턴은 함수가 선언되자마자 실행되도록 하는 문법이다.
  • 즉시 싱행 함수 함수 패턴은 다음의 부분들로 구성된다.
  • 함수를 함수 표현식으로 선언한다. (함수 선언문으로는 동작하지 않는다.)
  • 함수가 즉시 실행될 수 있도록 마지막에 괄호쌍으로 추가한다.
  • 전체 함수를 괄호로 감싼다. (함수를 변수에 할당하지 않을 경우에만 필요하다.)
  • 즉시 실행 함수는 모든 코드를 지역 유효범위로 감싸고 어떤 변수도 전역 유효범위로 새어나가지 않게 한다.

(function(){
var days = ['sun', 'mon', 'tue', 'wed', 'the', 'fri', 'sat'],
today = new Date();
msg = 'today is ' + days[today.getDay()] + ', ' + today.getDate();

console.log(msg)

}()) // 'today is tue, 6'



즉시 실행 함수의 매개변수


즉시 실행 함수에 인자를 전달할 수도 있다.

// 출력결과:
// 'i met joe black on Tue Aug 06 2019 14:59:48 GMT+0900 (대한민국 표준시)'
(function(who, when){

console.log('i met ' + who + ' on ' + when);

}('joe black', new Date()));


전역 객체를 global로 참조가 가능하다.


(function(global){

// 전역 객체를 'global' 로 참조
}(this));



즉시 실행 함수의 반환 값


다른 함수와 비슷하게 즉시 실행 함수도 값을 반환할 수 있고 반환된 값은 변수에 할당될 수 있다.

var result1 = (function(){
return 1 + 1;
}());

// 괄호를 생략해서 같은 동작을 구현할 수 있다.
var result2 = function(){
return 2 + 2;
}();

// 즉시실행 함수에서 미리 계산하여 클로저에 저장해둔 res라는 값을 반환
var getResult = (function(){
var res = 2+2;
return function(){
return res;
}
}())

console.log(getResult());


즉시 실행 함수는 객체 프로퍼티를 정의할 때에도 사용할 수 있다.


var o = {
message : (function(){
var who = 'me',
what = 'call';
return what + ' ' + who;
}()),
getMsg : function(){
return this.message;
}
};

// 사용방법
o.getMsg(); // call me
o.message; // call me


자기 자신을 정의하는 함수


함수는 동적으로 정의할 수 있고 변수에 할당할 수 있다. 어떤 면에서는 이전의 함수 포인터가 새로운 함수를 가리키도록 재사용하는 것이다.

var scareMe = function(){
console.log("boo");
scareMe = function(){
console.log("double boo");
};
};

// 자기 자신을 정의하는 함수를 사용
scareMe(); // boo
scareMe(); // double boo


이 패턴은 함수가 어떤 초기화 준비 작업을 단 한 번만 수행할 경우 유용하다.


단점은 자기 자신을 재정의한 이후에는 이전에 원본 함수에 추가했던 프로퍼티들을 모두 찾을 수 없게 된다는 점이다. 또한 함수가 다른 이름으로 사용된다면 재정의된 부분이 아니라 원본 함수의 본문이 실행된다.

1. 새로운 프로퍼티가 추가된다.

2. 함수 객체가 새로운 변수에 할당된다.

3. 함수는 메서드로도 사용된다.


var scareMe = function(){
console.log("boo");
scareMe = function(){
console.log("double boo");
};
};

// 자기 자신을 정의하는 함수를 사용
// scareMe(); // boo
// scareMe(); // double boo

// 1. 새로운 프로퍼티가 추가된다.
scareMe.property = "property";

// 2. 함수 객체가 새로운 변수에 할당된다.
var prank = scareMe;

// 3. 함수는 메서드로도 사용된다.
var spooky = {
boo : scareMe
}

// 새로운 이름으로 호출한다.
prank(); // boo
prank(); // boo
console.log(prank.property); // property

// 메서드로 호출한다.
spooky.boo(); // boo
spooky.boo(); // boo
console.log(spooky.boo.property); // property

// 자기 자신을 재정의한 함수를 사용한다.
scareMe(); // 'double boo'
scareMe(); // 'double boo'
console.log(scareMe.property); // undefined


예제에서 보는 것처럼 함수가 새로운 변수에 할당되면 예상과 달리 자기 자신을 정의하지 않는다.

콜백 패턴


함수는 객체다. 즉 함수를 다른 함수에 인자로 전달할 수 있다. introduceBugs() 함수를 writeCode() 함수의 인자로 전달하면, 아마도 writeCode()는 어느 시점에 introduceBugs()를 실행(또는 호출)할 것이다. 이때 introduceBugs()를 콜백함수 또는 간단하게 콜백이라고 부른다.

function writeCode(callback){
// 어떤작업을 수행한다.
callback();
// ...
}

function introduceBugs(){
console.log('bug');
}

writeCode(introduceBugs); // bug


introduceBugs()가 writeCode()의 인자로 괄호없이 전달된 사실을 눈여겨 보자. 괄호를 붙히면 함수가 실행되는데 이 경우에는 함수의 참조만 전달하고 실행은 추후 적절한 시점에 writeCode()가 해줄 것이기 때문에 괄호를 덧붙이지 않았다.



콜백 예제


아래 함수는 findNodes()와 같은 형식으로 호출되며 DOM 트리를 탐색해 필요한 엘리먼트의 배열을 반환한다.

var findNodes = function(callback){
var i = 100000, // 긴루프
nodes = [], // 결과를 저장할 배열
found; // 노드 탐색 결과

// 콜백 함수를 호출할 수 있는지 확인한다.
if(typeof callback !== "function"){
callback = false;
}

while(i){
i -= 1;
// 이 부분에 복잡한 로직이 들어간다.

// 여기서 콜백을 실행
if(callback){
callback(found)
}
nodes.push(found);
}
return nodes;
};

var hide = function(node){
nodes[i].style.display = "node";
}

// 노드를 찾아서 바로 숨긴다.
findNodes(hide);


이미 존재하는 함수를 콜백으로 함수로 쓸 수도 있지만 findNodes() 함수를 호출할 때 익명 함수를 생성해서 쓸 수도 있다.


// 익명함수를 콜백으로 전달한다.
findNodes(function(node){
node.style.display = "block";
})



콜백과 유효범위


만약 콜백 메서드가 자신이 속해있는 객체를 참조하기 위해 this를 사용하면 예상치 않게 동작할 수도 있다.
myapp이라는 객체의 메서드인 paint()함수를 콜백으로 사용한다고 가정해보자.

var myapp = {};
myapp.color = 'green';
myapp.paint = function(node){
node.style.color = this.color;
};

// findNodes() 함수는 이런 식으로 동작한다.

var findNodes = function(callback){
// ...
if(typeof callback === "function"){
callback(found)
}
}


findNodes(myapp.paint)를 호출하면 this.color가 정의되지 않아 예상대로 동작하지 않는다. findNodes()가 전역 함수이기 때문에 객체 this는 전역 객체를 참조한다. findNodes()가 (dom.findNodes()처럼) dom 이라는 객체의 메서드라면 콜백 내부의 this는 예상과는 달리 myapp이 아닌 dom을 참조하게 된다.


이 문제를 해결 하기 위해서는 콜백함수와 함께 콜백이 있는 객체를 전달하면 된다.


findNodes(myapp.paint, myapp);



var findNodes = function(callback, callback_obj){
// ...
if(typeof callback === "function"){
callback.call(callback_obj, found)
}
}


다음과 같이 바꿀수도 있다. 메서드와 객체를 전달할 때 메서드를 문자열로 전달하게 되면 객체를 두번 반복하지 않아도 된다.


findNodes("paint", myapp);

var findNodes = function(callback, callback_obj){
// ...
if(typeof callback === "string"){
callback = callback_obj[callback];
}

// ...
if(typeof callback === "function"){
callback.call(callback_obj, found)
}
}



비동기 이벤트 핸들러


페이지의 엘리먼트에 이벤트 리스너를 붙이는것도 콜백이 발생하는 것이므로 document 이벤트 리스너에 콜백을 전달 할 수 있다.

document.addEventListener("onload", console.log('resdy'), false);



타임아웃


setTimeout()과 setInterval()도 콜백 함수를 받아서 실행 한다.

var thePlotThickens = function(){
console.log('500ms later...');
};
setTimeout(thePlotThickens, 500);


함수의 name 프로퍼티


함수를 정의하는 패턴을 선택할 때는 읽기 전용인 name 프로퍼티를 쓸 일이 있는지도 고려해보아야 한다. name 프로퍼티는 표준이 아니지만 많은 실행 환경에서 사용이 가능하다.

function foo(){} // 함수 선언문
var bar = function(){} // 함수 표현식
var baz = function baz(){} // 기명 함수 표현식

console.log(foo.name); // foo
console.log(bar.name); // ""
console.log(baz.name); // baz



함수 호이스팅


모든 변수는 함수 본문 어느 부분에서 선언되더라도 내부적으로 함수의 맨 윗부분으로 끌어올려 진다. 함수 또한 결국 변수에 할당되는 객체이기 때문에 동일한 방식이 적용된다. 함수 선언문을 사용하면 변수 선언뿐 아니라 함수 정의 자체도 호이스팅되기 대문에 자칫 오류를 만들어내기 쉽다.

// 안티패턴이다.
// 전역 함수
function foo(){
console.log("global foo");
}

function bar(){
console.log("global bar");
}

function hoistMe(){

console.log(typeof foo); //function
console.log(typeof bar); // undefined

foo(); //local foo"
bar(); // type error : bar is not a function

// 함수 선언문;
// 변수 'foo'와 정의된 함수 모두 호이스팅 된다.
function foo(){
console.log('local foo');
}

// 함수 표현식
// 변수 'bar' 는 호이스팅 되지만 정의된 함수는 호이스팅되지 않는다.
var bar = function(){
console.log('local bar');
}
}
hoistMe();


보다시피, hoistMe() 함수 내에서 foo와 bar를 정의하면, 실제 변수를 정의한 위치와 상관없이 끌어올려져 전역변수인 foo와 bar를 덮어쓰게 된다. 그런데 지역변수 foo()는 나중에 정의되어도 상단으로 호이스팅되어 정상 동작하는 반면, bar()의 정의는 호이스팅 되지 않고 선언문만 호이스팅 된다. 때문에 bar()의 정의는 호이스팅 되지 않고 선언문만 호이스팅된다. 때문에 bar()의 정의가 나오기 전까지는 undefined 상태이고, 따라서 함수로 사용할 수도 없다. 또한 선언문 자체는 호이스팅 되었기 때문에 유효범위 체인 내에서 전역 bar()도 보이지 않는다.

에러 객체


자바스크립트에는 Error(), SyntaxError(), TypeError() 등 여러 가지 에러 생성자가 내장되어 있으며 throw 문과 함께 사용된다.
이 생성자들을 통해 생성된 에러 객체들은 다음과 같은 프로퍼티를 가진다.

name
- 객체를 생성한 생성자 함수의 name 프로퍼티. 범용적인 'Error' 일 수도 있고 'RangError'와 같이 좀더 특화된 생성자일 수도 있다.

message
- 객체를 생성할 때 생성자에 전달된 문자열

throw문은 어떤 객체와도 함께 사용할 수 있다. 이 객체는 'name', 'message' 외에도 임의의 프로퍼티를 가질수 있기 때문에 catch문에서 처리할 정보를 담아 전달하면된다.

var num = 2;

try{
// 에러를 발생시킨다.
if(num === 2){
throw{
name : "MyErrorType", // 임의의 에러타입
message : "user message",
extra : "extra message",
remedy : genericErrorHandler // 에러 처리 함수
};
}
}catch(e){
// 사용자에게 공지
console.log(e.name);
console.log(e.message);
console.log(e.extra);

// 에러처리 함수 호출
e.remedy();
}
function genericErrorHandler(){
console.log('genericErrorHandler 입니다.');
}

/*
'MyErrorType'
'user message'
'extra message'
'genericErrorHandler 입니다.'
*/



아래와 같이 Error type 별로 세분화 할 수 있다.

try {
myroutine(); // may throw three types of exceptions
} catch (e) {
if (e instanceof TypeError) {
// statements to handle TypeError exceptions
} else if (e instanceof RangeError) {
// statements to handle RangeError exceptions
} else if (e instanceof EvalError) {
// statements to handle EvalError exceptions
} else {
// statements to handle any unspecified exceptions
logMyErrors(e); // pass exception object to error handler
}
}


JSON 다루기


JSON은 자바스크립트 객체 표기법(JavaScript Object Notation)의 준말로 데이터 전송 형식의 일종이다.
JSON을 다룰때는 JSON.parse()를 사용하는것이 최선책이다.

// 입력되는 JSON 문자열
var jstl = '{"mykey": "myValue"}';

// 안티 패턴
var data = eval('(' + jstl + ')');
console.log(data.mykey);

// 권장안
var data = JSON.parse(jstl);
console.log(data.mykey);


JUI3을 사용하면 다음과 같이 쓸 수 있다.


// 입력되는 JSON 문자열
var jstl = '{"mykey": "myValue"}';

// JUI 인스턴스를 사용하여 문자열을 파싱하고 객체로 변환한다.
YUI().use('json-parse', function(Y){
var data = Y.JSON.parse(jstl);
console.log(data.mykey); // myValue
})


jquery에는 parse.JSON() 이라는 메서드가 있다.


// 입력되는 JSON 문자열
var jstl = '{"mykey": "myValue"}';

var data = jQuery.parseJSON(jstl);
console.log(data.mykey); // 'myValue'


JSON.parse() 메서드의 반대는 JSON.stringify()다. 이 메서드는 객체 또는 배열을 인자로 받아 JSON 문자열로 직렬화 한다.


var dog = {
name : "fido",
dob: new Date(),
legs: [1,2,3,4]
};

var jsonstr = JSON.stringify(dog);
console.log(jsonstr);
// '{"name":"fido","dob":"2019-08-02T07:29:35.248Z","legs":[1,2,3,4]}'


+ Recent posts