요즘 개인적으로 자바스크립트를 공부하고 있다. 하지만 내 경우 어정쩡하게 알고 있는 자바스크립트라 기초책은 보나마나인데, 이 책은 정말 실무에서도 바로 쓸 수 있는 패턴을 뽑아서 먹여주는 책 같다. 이 책과 더불어 자바스크립트 성능 최적화도 보면 정말 좋겠다. 아무튼 이 책을 요약하면서 정리하고자 한다. 이 책은 정말 강력 추천하며 자바스크립트를 제대로 학습하기 위한 필수 소장서이다.
책구입 : http://tinyurl.com/7ejd4rs
출판사 책소개 : http://blog.insightbook.co.kr/245
자바스크립트 코딩기법과 핵심패턴 제 6장 코드 재사용 패턴 #2
이 책에서는 자바스크립트에서 코드 재사용 패턴은 상속, 다른 객체와 합성, 믹스-인 객체 사용, 메서드 빌려쓰기등으로 소개하고 있다. 코드 재사용 작업을 접근할 때, GoF의 충고인 '클래스 상속보다 객체 합성을 우선시하라'를 생각하는게 중요하다.
지난 글(http://welchsy.tistory.com/236) 에서 코드 재사용 패턴중 클래스 방식의 상속에 대해서 다루었다. 나머지는 여기서 다룬다.
프로토타입을 활용한 상속
프로토타입을 활용한 상속은 클래스를 사용하지 않는 '새로운' 방식의 패턴이다.
다음과 같은 함수가 이것을 실현시킨다.
1.
// 프로토타입 활용한 상속을 가능케하는 함수
2.
function
object(o) {
3.
function
F() {};
4.
F.prototype = o;
5.
return
new
F();
6.
}
위 함수를 아래처럼 사용할 수 있다.
01.
// 상속할 객체
02.
var
parent = {
03.
name:
"Papa"
,
04.
getName:
function
() {
05.
return
this
.name;
06.
}
07.
};
08.
// 새로운 객체
09.
var
child = object(parent);
10.
//테스트
11.
console.log(child.name);
//"Papa"
12.
console.log(child.getName());
//"Papa"
위 코드처럼 부모를 객체 리터럴로 생성하는 것 뿐만 아니라 생성자 함수를 통해서도 부모를 생성할 수 있다.
01.
//부모 생성자
02.
function
Person() {
03.
// 부모 생성자 자신의 프로퍼티
04.
this
.name =
"Adam"
;
05.
}
06.
// 프로토타입에 추가된 프로퍼티
07.
Person.prototype.getName =
function
() {
08.
return
this
.name;
09.
};
10.
// Person 인스턴스 생성
11.
var
papa =
new
Person();
12.
// 이 인스턴스를 상속
13.
var
kid = object(papa);
14.
// 부모 자기 자신의 프로퍼티와 프로토타입의 프로퍼티가 모두 상속되었는지 확인
15.
console.log(kid.getName());
//"Adam"
하지만 주의할 것은 생성자 함수의 프로토타입 객체만 상속받게 할 수 있다.
2.
console.log(
typeof
kid2.getName);
//"function" 이 메서드는 프로토타입 안에 정의된 프로퍼티이다.
3.
console.log(
typeof
kid2.name);
//"undefined" 프로토타입만 상속했기 때문에 부모에 정의된 name 프로퍼티는 상속되지 않음
ECMAScript 5에는 Object.create()가 위 object() 함수를 구현하고 있다.
1.
var
parent =
new
Person();
2.
var
child2 = Object.create(parent, {
3.
age: { value: 2 }
//ECMA 5 기술자(Descriptor)
4.
});
5.
console.log(child2.hasOwnProperty(
"age"
));
//true
자바스크립트 라이브러리에서 YUI3에서도 Y.Object() 메서드가 그것을 구현하고 있음을 알아두자.
책 내용에는 없지만 위 방식을 그대로 사용하는 것은 부정적이다. 이것은 여전히 전역을 더럽히기 때문에 네임스페이스 패턴이든 샌드박스 패턴이든 사용해 전역을 최소화할 필요가 있겠다. 하지만 기존 클래스 방식의 상속보다 훨씬 간단하면서도 매끄럽게 재사용 패턴을 적용할 수 있다는 점은 크게 매력적이다.
프로퍼티 복사를 통한 상속패턴
아래는 얕은 복사 방식이다.
01.
//프로퍼티 얕은 복사를 통한 상속 패턴 적용 함수
02.
function
extend(parent, child) {
03.
var
i,
04.
child = child || {};
05.
for
( i
in
parent ) {
06.
if
( parent.hasOwnProperty(i) ) {
07.
child[i] = parent[i];
08.
}
09.
}
10.
return
child;
11.
}
12.
13.
//프로퍼티 복사 확인
14.
var
dad = {name:
"Adam"
};
15.
var
kid = extend(dad);
16.
console.log(kid.name);
//"Adam"
17.
18.
//프로퍼티 얕은 복사 확인
19.
var
dad2 = {
20.
counts: [1, 2, 3],
21.
reads: {paper:
true
}
22.
};
23.
var
kid2 = extend(dad2);
24.
kid2.counts.push(4);
25.
console.log(kid2.counts.toString());
//"1,2,3,4"
26.
console.log(dad2.reads === kid2.reads);
//true
아래 코드는 프로퍼티 깊은 복사를 통한 상속 패턴 적용 함수이다.
01.
function
extendDeep(parent, child) {
02.
var
i,
03.
toStr = Object.prototype.toString;
04.
astr =
"[object Array]"
;
05.
child = child || {};
06.
for
(i
in
parent) {
07.
if
(parent.hasOwnProperty(i)){
08.
if
(
typeof
parent[i] ===
"object"
) {
09.
child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
10.
extendDeep(parent[i], child[i]);
11.
}
else
{
12.
child[i] = parent[i];
13.
}
14.
}
15.
}
16.
return
child;
17.
}
18.
19.
var
dad = {
20.
counts: [1, 2, 3],
21.
reads: {paper:
true
}
22.
};
23.
var
kid = extendDeep(dad);
24.
kid.counts.push(4);
25.
console.log(kid.counts.toString());
//"1,2,3,4"
26.
console.log(dad.counts.toString());
//"1,2,3"
27.
console.log(dad.reads === kid.reads);
// false
28.
kid.reads.paper =
false
;
29.
console.log(dad.reads.paper);
//true
위 함수들은 아주 간단하고 널리 사용된다고 한다. 그리고 jQuery의 extend() 메서드는 깊은 복사를 하고 Y.clone() 깊은 복사를 수행하면서 함수도 복사해 자식 객체와 바인딩 해준다고 한다. 이 패턴은 프로토타입을 전혀 사용하지 않은 것도 주목할 만하다.
하지만 내 생각에.... 깊은 복사를 하는 과정에서 extendDeep()을 재귀적으로 호출하고 있다. 이 점은 자바스크립트 특성상 이렇게 쓰는 경우 스택오버가 걸릴 가능성이 농후하므로 뭔가 비동기적으로 동작하도록 만들 필요가 있다.
게다가 배열이 for-in 루프로 요소를 탐색하는 것은 2장 기초에서 다루었듯이 for 루프를 사용하는 것이 맞다.
믹스-인
프로퍼티 복사 아이디어를 발전시켜 믹스-인 패턴을 생각할 수 있다. 이것은 하나의 객체를 복사하는게 아니라 여러 객체를 복사해 하나의 객체에 섞어 넣을 수 있다.
01.
function
mix() {
02.
var
arg, prop, child = {};
03.
for
(arg = 0; arg < arguments.length; arg += 1) {
04.
for
(prop
in
arguments[arg]) {
05.
if
(arguments[arg].hasOwnProperty(prop)) {
//프로토타입 프로퍼티를 걸러냄
06.
child[prop] = arguments[arg][prop];
07.
}
08.
}
09.
}
10.
return
child;
11.
}
12.
var
cake = mix(
13.
{eggs: 2, large:
true
},
14.
{butter: 1, salted:
true
},
15.
{flour:
"3 cups"
},
16.
{sugar:
"sure!"
}
17.
);
18.
console.dir(cake);
결과적으로 아래처럼 나온다.
butter: 1
eggs: 2
large: true
salted: true
flour: "3 cups"
sugar: "sure!"
믹스-인 개념에는 단순히 루프를 돌고, 프로퍼티를 복사하는 것이기 때문에 부모들과의 연결 고리는 끊어진 상태이다.
개인적인 이 패턴에 대한 생각을 남기면... 위 mix() 메서드는 여러개의 객체중에 프로퍼티 이름이 중복되면 마지막에 들어간 것이 기존에 있는 것을 덮어쓰게 될 것이다.
게다가 다음과 같은 경우에는 대응하지 못한다.
01.
var
a = {array: [1,2,3]};
02.
var
cake = mix(
03.
{eggs: 2, large:
true
},
04.
{butter: 1, salted:
true
},
05.
{flour:
"3 cups"
},
06.
{sugar:
"sure!"
},
07.
a
08.
);
09.
a.array.push(4);
10.
console.log(a.array.toString());
//"1,2,3,4"
11.
console.log(cake.array.toString());
//"1,2,3,4"
원래 기대하는 바는 cake.array.toString()의 경우 "1,2,3"이어야 할 것이다. 즉, 배열값에 대해서는 얕은 복사를 했으므로 깊은 복사를 할 수 있도록 개선해야한다.
메서드 빌려쓰기
메서드 빌려쓰기 재사용 패턴은 정말 자바스크립트의 특징을 대변해주는 패턴일 것이다. 이 패턴은 부모-자식 관계까지 만들지 않고 어떤 객체의 메서드 한두개만 빌려쓰는데 유용하다.
책 내용중에는 apply와 call을 사용해 bind를 구축하는 방법과 this문제를 잘 다루었다. 마지막에 ECMAScript 5부터 지원하는 Function.prototype.bind() 메서드를 사용하면 된다고 했다. 하지만 이 메서드가 지원되지 않은 경우도 감안해서 아래와 같은 코드를 쓰면 언제든지 bind()를 활용할 수 있게 된다.
01.
if
(
typeof
Function.prototype.bind ===
"undefined"
) {
02.
Function.prototype.bind =
function
(thisArg) {
03.
var
fn =
this
,
04.
slice = Array.prototype.slice,
05.
args = slice.call(argments, 1);
06.
return
function
() {
07.
return
fn.apply(thisArg, args.concat(slice.call(arguments)));
08.
};
09.
}
10.
}
메서드 빌려쓰기 패턴은 잘쓰면 꽤 유용할 듯 싶다.
'JavaScript' 카테고리의 다른 글
자바스크립트에서 this란? (0) | 2013.05.09 |
---|---|
자바스크립트의 typeof 연산자에 대해 (0) | 2013.05.09 |
자바스크립트 코딩기법과 핵심패턴 제 6장 코드 재사용 패턴 #1 (0) | 2013.05.09 |
자바스크립트 코딩기법과 핵심패턴 제 5장 객체 생성 패턴 (0) | 2013.05.09 |
자바스크립트 코딩기법과 핵심패턴 제 4장 함수 (0) | 2013.05.09 |