3. 3
1. 생성자 방식의 상속
코드 재사용 패턴
● 기존의 코드가 훌륭함 + 테스트를 마침 + 유지보수 및 확장하기 좋음 + 문서화 되어있음
기존 코드를
최대한 재활용하고
새로 작성하는 코드는 최소화
하는 노력은 당연
● 상속은 코드 재사용 방법 중 하나
● 생성자 방식의 상속
4. 4
생성자 방식의 상속
첫 번째 패턴(기본 패턴)
1.
1. Parent() 생성자를 이용해 객체 생성
2. 이 객체를 Child의 프로토타입에 할당
3. 재사용 가능한 inherit() 함수 구현
// 부모 생성자
function Parent(name) {
this.name = name || 'Adam';
}
// 생성자의 프로토타입에 기능을 추가
Parent.prototype.say = function() {
return this.name;
}
// 아무내용이 없는 자식 생성자
function Child(name) {}
// 상속 처리
function inherit(C, P) {
C.prototype = new P();
}
inherit(Child, Parent);
var kid = new Child();
kid.say(); // "Adam"
kid.name = "Patrick";
kid.say(); // "Patrick"
//Delete kid.name으로 새로운 프로퍼티 삭제시, "Adam"
5. 5
생성자 방식의 상속
두 번째 패턴(생성자 빌려쓰기)
● 첫번째 패턴의 단점
- 부모 객체의 this에 추가된 객체 자신의 프로퍼티와 프로토타입 프로퍼티 모두 물려 받는 점이 단점
대부분의 경우 객체 자신의 프로퍼티는 특정 인스턴스에 한정되어 재사용할 수 없기에 필요 없다
- inherit() 함수가 인자를 처리 못하는 점이 단점
var s = new Child('Seth');
s.say(); // "Adam"
● 자식에서 부모로 인자를 전달하지 못했던 첫 번째 패턴 문제 해결
function Child(a, b, c, d) {
Parent.apply(this, arguments);
}
1.
6. 6
생성자 방식의 상속
두 번째 패턴(생성자 빌려쓰기)
1.
// 부모 생성자
function Parent(name) {
this.name = name || 'Adam';
}
// 생성자의 프로토타입에 기능을 추가
Parent.prototype.say = function() {
return this.name;
}
// 자식 생성자
function Child(name) {
Parent.apply(this, arguments);
}
var kid = new Child("Patrick");
kid.name; // Patrick
typeof kid.say; // undefined
● 이 패턴은 부모 생성자 함수의 this에 자식 객체를 바인딩한 다음,
● 자식 생성자가 받은 인자들을 모두 넘겨줌
● 이렇게 하면 부모 생성자 함수 내부의 this에 추가된 프로퍼티만 물려받게 됨
● 프로토타입에 추가된 멤버는 상속되지 않음
● 생성자 빌려쓰기 패턴을 사용하면, 자식 객체는 상속된 멤버의 복사본을 받게 됨
7. 7
생성자 방식의 상속
두 번째 패턴(생성자 빌려쓰기)
● 생성자 빌려쓰기 패턴을 사용하여
생성자를 하나 이상 빌려 쓰는 다중 상속을 구현
function Cat() {
this.legs = 4;
this.say = function() {
return "meaowww";
}
}
function Bird() {
this.wings = 2;
this.fly = true;
}
function CatWings() {
Cat.apply(this);
Bird.apply(this);
}
var jane = new CatWings();
jane.fly; //true
jane.legs; //4
jane.wings; //2
jane.say; //function()
1.
8. 8
생성자 방식의 상속
세 번째 패턴(생성자 빌려쓰고 프로토타입 지정)
● 첫 번째 패턴의 단점
- 부모 객체의 this에 추가된 객체 자신의 프로퍼티와 프로토타입 프로퍼티 모두 물려 받는 점이 단점
대부분의 경우 객체 자신의 프로퍼티는 특정 인스턴스에 한정되어 재사용할 수 없기에 필요 없다
- inherit() 함수가 인자를 처리 못하는 점이 단점
● 두 번째 패턴의 장단점
- 부모 생성자 자신의 멤버에 대한 복사본을 가져올 수 있다는 점이 장점
- 자식이 실수로 부모의 프로퍼티 덮어쓰는 위험 방지할 수 있다는 점이 장점
- 프로토타입이 전혀 상속되지 않는 점이 단점
● 세 번째 패턴
- 앞의 두 패턴의 결합
- 먼저 부모 생성자를 빌려온 후,
자식의 프로토타입이 부모 생성자를 통해 생성된 인스턴스를 가리키도록 지정
function Cild(a, b, c, d) {
Parent.apply(this, arguments);
}
Child.prototype = new Parent();
1.
9. 9
생성자 방식의 상속
세 번째 패턴(생성자 빌려쓰고 프로토타입 지정)
1.
● 자식 객체는 부모가 가진 자신만의 프로퍼티의 복사본을 가지게 됨
● 부모의 프로토타입 멤버로 구현된 재사용가능한 기능들에 대한 참조 또한 물려받음
● 부모가 가진 모든 것을 상속하는 동시
● 부모의 프로퍼티를 덮어쓸 위험 없이 자신만의 프로퍼티를 마음 놓고 변경 가능
// 부모 생성자
function Parent(name) {
this.name = name || 'Adam';
}
// 생성자의 프로토타입에 기능을 추가
Parent.prototype.say = function() {
return this.name;
}
// 자식 생성자
function Child(name) {
Parent.apply(this, arguments);
}
Child.prototype = new Parent();
var kid = new Child("Patrick");
kid.name; // Patrick
kid.say(); // Patrick
delete kid.name;
kid.say(); // Adam
10. 10
생성자 방식의 상속
네 번째 패턴(프로토타입 공유)
● 첫 번째 패턴의 단점
- 부모 객체의 this에 추가된 객체 자신의 프로퍼티와 프로토타입 프로퍼티 모두 물려 받는 점이 단점
대부분의 경우 객체 자신의 프로퍼티는 특정 인스턴스에 한정되어 재사용할 수 없기에 필요 없다
- inherit() 함수가 인자를 처리 못하는 점이 단점
● 두 번째 패턴의 장단점
- 부모 생성자 자신의 멤버에 대한 복사본을 가져올 수 있다는 점이 장점
- 자식이 실수로 부모의 프로퍼티 덮어쓰는 위험 방지할 수 있다는 점이 장점
- 프로토타입이 전혀 상속되지 않는 점이 단점
● 세 번째 패턴의 장단점
- 앞의 두 패턴의 결합
- 부모가 가진 프로퍼티의 복사본을 가져올 수 있다는 점이 장점
- 프로토타입으로 구현된 재사용 기능을 참조할 수 있다는 점이 장점
- 부모 생성자를 비효율적으로 두 번 호출하는 점이 단점
● 네 번째 패턴
- 부모생성자 한번도 호출하지 않음
1.
11. 11
생성자 방식의 상속
네 번째 패턴(프로토타입 공유)
1.
function inherit(C, P) {
C.prototype = P.prototype;
}
● 모든 객체가 동일한 프로토타입을 공유하게 됩니다.
● 프로토타입 체인 검색이 짧고 간단해집니다.
● 상속 체인의 하단 어딘가에 있는 자식이난 손자가 프로토타입을 수정할 경우,
모든 부모와 손자뻘의 객체에 영향을 줍니다.
● 부모와 자식 객체가 모두 동일한 프로토타입을 공유
● say() 메서드에도 똑같은 접근 권한
● 자식 객체는 name 프로퍼티를 물려받지 않음
12. 12
생성자 방식의 상속
다섯 번째 패턴(임시 생성자)
● 첫 번째 패턴의 단점
- 부모 객체의 this에 추가된 객체 자신의 프로퍼티와 프로토타입 프로퍼티 모두 물려 받는 점이 단점
대부분의 경우 객체 자신의 프로퍼티는 특정 인스턴스에 한정되어 재사용할 수 없기에 필요 없다
- inherit() 함수가 인자를 처리 못하는 점이 단점
● 두 번째 패턴의 장단점
- 부모 생성자 자신의 멤버에 대한 복사본을 가져올 수 있다는 점이 장점
- 자식이 실수로 부모의 프로퍼티 덮어쓰는 위험 방지할 수 있다는 점이 장점
- 프로토타입이 전혀 상속되지 않는 점이 단점
● 세 번째 패턴의 장단점
- 앞의 두 패턴의 결합
- 부모가 가진 프로퍼티의 복사본을 가지게 되어 덮어쓰는 위험 방지할 수 있다는 점이 장점
- 프로토타입으로 구현된 재사용 기능을 참조할 수 있다는 점이 장점
- 부모 생성자를 비효율적으로 두 번 호출하는 점이 단점
● 네 번째 패턴
- 부모생성자 한번도 호출하지 않는 점이 장점
- 상속 체인의 하단 어딘가에 있는 자식이난 손자가 프로토타입을 수정할 경우,
모든 부모와 손자뻘의 객체에 영향을 주는 점이 단점
● 다섯번째 패턴
- 프로토타입 체인의 이점은 유지하면서 동일한 프로토타입을 공유할 때 문제를 해결
1.
13. 13
생성자 방식의 상속
다섯 번째 패턴(임시 생성자)
1.
● 부모와 자식의 프로토타입 사이에 직접적인 링크를 끊음
function inherit(C, P) {
var F = function () {};
F.prototype = P.prototype;
C.prototype = new F();
}
var kid = new Child();
kid.name; // undefined
● 자식이 프로토타입의 프로퍼티만을 물려 받음
● 생성자에서 this에 추가한 멤버는 상속되지 않음
● kid.say() 접근하면 3번 객체에서 찾을수 없어 프로토타입 체인을 따라 4번 객체, 1번 객체에서 탐색하고 사용
● Parent()를 상속하는 모든 생성자와 이를 통해 생성되는 모든 객체들은 똑같이 이 메서드를 사용
14. 14
생성자 방식의 상속
다섯 번째 패턴(임시 생성자)
● 상위 생성자 저장
- 부모 원본에 대한 참조를 추가할 수도 있음
- 상위 생성자에 대한 접근 경로를 가지는 것과 같음
- 이 프로퍼티는 uber 로 많이 사용
function inherit(C, P) {
var F = function () {};
F.prototype = P.prototype;
C.prototype = new F();
C.uber = P.prototype;
}
● 생성자 포인터 재설정
- 나중을 위해 생성자 함수를 가리키는 포인터를 재설정하는 것
- 생성자 포인터를 재설정하지 않으면 모든 자식 객체들의 생성자는 Parent()로 지정돼 있음
- 그러므로 생성자 함수를 자식 자신을 가리킬 수 있도록 아래처럼 코드를 추가한다
// 부모와 자식을 두고 상속관계
function Parent() {}
function Child() {}
inherit(Child, Parent);
// 생성자를 확인
var kid = new Child();
kid.constructor.name; // Parent
kid.constructor === Parent; // ture
function inherit(C, P) {
var F = function () {};
F.prototype = P.prototype;
C.prototype = new F();
C.uber = P.prototype;
C.prototype.constructor = C;
}
1.
15. 15
생성자 방식의 상속
다섯 번째 패턴(임시 생성자)
● 최적화 방향
- 임시 생성자(프록시 생성자)가 상속이 필요할 때마다 생성되지 않게 하는 것
- 임시 생성자는 한 번만 만들어두고 임시 생성자의 프로토타입만 변경
- 즉시 실행 함수 활용하여 프록시 함수를 클로저 안에 저장 가능
● 최종
var inherit = (function() {
var F = function () {};
return function(C, P) {
F.prototype = P.prototype;
C.prototype = new F();
C.uber = P.prototype;
C.prototype.constructor = C;
}
}());
1.