서론
JS 의 OOP 를 좋아하지 않았습니다. 학습 단계에서는 거의 쓸 일이 없었고, JS 의 OOP 는 어중간하게 느껴졌습니다. 아마 Java 를 먼저 접했기 때문에 JS 의 OO 부분에 약간의 거부감이 있었을 것입니다.
편견은 편견일 뿐, 면접관이 JS 의 OOP 에 대해 질문한다는 것은 분명 유용한 것이 있다는 뜻입니다. 편견을 버리고 진지하게 이해해야 합니다.
약정
P.S. 아래에 이어지는 것은 다소 긴 이야기이므로, 미리 공통 언어를 약정해 둡니다:
/*
* 약정
*/
function Fun(){
// 프라이빗 속성
var val = 1; // 프라이빗 기본 속성
var arr = [1]; // 프라이빗 참조 속성
function fun(){} // 프라이빗 함수 (참조 속성)
// 인스턴스 속성
this.val = 1; // 인스턴스 기본 속성
this.arr = [1]; // 인스턴스 참조 속성
this.fun = function(){}; // 인스턴스 함수 (참조 속성)
}
// 프로토타입 속성
Fun.prototype.val = 1; // 프로토타입 기본 속성
Fun.prototype.arr = [1]; // 프로토타입 참조 속성
Fun.prototype.fun = function(){}; // 프로토타입 함수 (참조 속성)
위의 약정은 비교적 합리적이라고 생각합니다. 이해하기 어렵다면 [黯羽輕揚:JS 학습노트 2_객체지향](http://ayqy.net/blog/JS 学习笔记 2_面向对象/) 을 참조하여 더 많은 기본 상식을 이해하시기 바랍니다.
일. 단순 프로토타입 체인
이것은 상속을 구현하는 가장 간단한 방법입니다. 정말 초간단하며, 핵심은 한 마디뿐입니다 (코드 중에 주석으로 명시했습니다)
1. 구체적인 구현
function Super(){
this.val = 1;
this.arr = [1];
}
function Sub(){
// ...
}
Sub.prototype = new Super(); // 핵심
var sub1 = new Sub();
var sub2 = new Sub();
sub1.val = 2;
sub1.arr.push(2);
alert(sub1.val); // 2
alert(sub2.val); // 1
alert(sub1.arr); // 1, 2
alert(sub2.arr); // 1, 2
2. 핵심
부모 클래스 인스턴스를 서브클래스의 프로토타입 객체로 사용함
3. 장단점
장점:
- 간단하고 구현이 쉬움
단점:
- sub1.arr 을 수정하면 sub2.arr 도 변함. 프로토타입 객체에서의 참조 속성은 모든 인스턴스가 공유하기 때문.
이렇게 이해할 수 있습니다: sub1.arr.push(2); 를 실행하면 먼저 sub1 에 대해 속성 검색을 수행합니다. 인스턴스 속성을 모두 찾아봅니다 (본 예에서는 인스턴스 속성이 없음). 찾지 못하면 프로토타입 체인을 따라 sub1 의 프로토타입 객체에 도달하고, 검색해보니 arr 속성이 있습니다. 그래서 arr末尾에 2 를 삽입하므로 sub2.arr 도 변합니다.
- 서브클래스 인스턴스를 생성할 때 부모 클래스 생성자에 인수를 전달할 수 없음
이. 생성자 빌림
단순 프로토타입 체인은 정말 간단하지만, 2 가지 치명적인 단점이 있어 사용할 수 없습니다. 그래서 20 세기 말의 jsers 들이 이 2 가지 결함을 고치는 방법을 고안해냈고, 생성자 빌림 방식이 탄생했습니다.
1. 구체적인 구현
function Super(val){
this.val = val;
this.arr = [1];
this.fun = function(){
// ...
}
}
function Sub(val){
Super.call(this, val); // 핵심
// ...
}
var sub1 = new Sub(1);
var sub2 = new Sub(2);
sub1.arr.push(2);
alert(sub1.val); // 1
alert(sub2.val); // 2
alert(sub1.arr); // 1, 2
alert(sub2.arr); // 1
alert(sub1.fun === sub2.fun); // false
2. 핵심
부모 클래스의 생성자를 빌려 서브클래스 인스턴스를 강화함. 즉, 부모 클래스의 인스턴스 속성을 서브클래스 인스턴스에 복사하여 장착한 것입니다 (프로토타입은 전혀 사용하지 않음)
3. 장단점
장점:
-
서브클래스 인스턴스가 부모 클래스 참조 속성을 공유하는 문제 해결
-
서브클래스 인스턴스를 생성할 때 부모 클래스 생성자에 인수를 전달할 수 있음
P.S. 선배들은 이렇게 효율적이어서, 두 가지 결함을 순식간에 고쳤습니다.
단점:
- 함수 재사용을 구현할 수 없음. 각 서브클래스 인스턴스가 새로운 fun 함수를 가지고 있어서, 많아지면 성능에 영향을 미치고 메모리가 폭발합니다..
P.S. 글쎄요, 참조 속성 공유 문제를 고친ばかり인데, 또 이 새로운 문제가 발생했습니다..
삼. 조합 상속 (가장 일반적)
현재 우리의 생성자 빌림 방식에는 아직 문제가 있습니다 (함수 재사용을 구현할 수 없음). 괜찮습니다. 계속 고칩니다. jsers 들이 고생해서 조합 상속을 만들어냈습니다.
1. 구체적인 구현
function Super(){
// 여기서만 기본 속성과 참조 속성 선언
this.val = 1;
this.arr = [1];
}
// 여기서 함수 선언
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
//Super.prototype.fun3...
function Sub(){
Super.call(this); // 핵심
// ...
}
Sub.prototype = new Super(); // 핵심
var sub1 = new Sub(1);
var sub2 = new Sub(2);
alert(sub1.fun === sub2.fun); // true
2. 핵심
인스턴스 함수는 모두 프로토타입 객체 위에 배치하여 함수 재사용을 구현함. 동시에 생성자 빌림 방식의 장점도 유지함. Super.call(this); 를 통해 부모 클래스의 기본 속성과 참조 속성을 상속하고 인수를 전달할 수 있는 장점을 유지. Sub.prototype = new Super(); 를 통해 부모 클래스 함수를 상속하여 함수 재사용을 구현.
3. 장단점
장점:
- 참조 속성 공유 문제가 존재하지 않음
- 인수 전달 가능
- 함수 재사용 가능
단점:
- (약간의 결함) 서브클래스 프로토타입 위에 여분의 부모 클래스 인스턴스 속성이 존재함. 부모 클래스 생성자가 두 번 호출되어 두 개가 생성되었고, 서브클래스 인스턴스 위의 것이 서브클래스 프로토타입 위의 것을 가림... 이것도 메모리 낭비이며, 이전 상황보다는 낫지만 확실히 결함입니다.
P.S. 이 '여분'을 이해할 수 없다면 [黯羽輕揚:JS 학습노트 2_객체지향](http://ayqy.net/blog/JS 学习笔记 2_面向对象/) 을 참조하시기 바랍니다. 글 마지막에 더 자세한 설명이 있습니다.
사. 파라사이틱 조합 상속 (최상의 방식)
이름에서도 알 수 있듯이 또 조합 상속의 최적화입니다. 조합 상속이 결함이 있다고 했지만, 괜찮습니다. 완벽을 계속 추구합니다.
1. 구체적인 구현
function beget(obj){ // 자식을 낳는 함수 beget: 용은 용을 beget, 봉은 봉을 beget.
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
// 여기서만 기본 속성과 참조 속성 선언
this.val = 1;
this.arr = [1];
}
// 여기서 함수 선언
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
//Super.prototype.fun3...
function Sub(){
Super.call(this); // 핵심
// ...
}
var proto = beget(Super.prototype); // 핵심
proto.constructor = Sub; // 핵심
Sub.prototype = proto; // 핵심
var sub = new Sub();
alert(sub.val);
alert(sub.arr);
P.S. 잠깐만요, 자식을 낳는 함수가 뭐예요? 들어본 적이 없는데요. 그리고 핵심이라고 명시된 3 문장도 왜 이해가 안 갈까요? 조급해하지 마세요. 차 한 잔 마시고 계속 보겠습니다.
2. 핵심
beget(Super.prototype); 를 사용하여프로토타입 객체 위의 여분인 부모 클래스 인스턴스 속성을 잘라냄
P.S. 뭐요? 이해가 안 된다고요? 아아~, 프로토타입식 상속과 파라사이틱 상속을 소개하는 것을 잊었군요. 문 단속을 잊었다고 했던 게.. 이 기억력.
P.S. 파라사이틱 조합 상속, 이 이름은 그다지 적절하지 않습니다. 파라사이틱 상속과 관계가 그다지 크지 않아요.
3. 장단점
장점: 완벽합니다.
단점: 이론적으로는 없습니다 (사용하기 번거로운 것을 단점으로 치지 않는다면..)
P.S. 사용하기 번거로운 것은 한 측면입니다. 다른 한편으로는 파라사이틱 조합 상속이 비교적 늦게 등장한 것으로, 21 세기 초의 것입니다.大家都 이렇게 오래 기다릴 수 없어서, 조합 상속이 가장 일반적으로 사용되고, 이 이론적으로 완벽한 방식은 교과서상의 최상의 방식일 뿐입니다.
오. 프로토타입식
실제로 위의 완벽한 방식을 소개하면 끝낼 수 있지만, 조합 상속에서 완벽한 방식으로의 사고 도약이 꽤 큰 것 같아, 이야기를 명확히 할 필요가 있습니다.
1. 구체적인 구현
function beget(obj){ // 자식을 낳는 함수 beget: 용은 용을 beget, 봉은 봉을 beget.
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
this.val = 1;
this.arr = [1];
}
// 부모 클래스 객체 획득
var sup = new Super();
// 자식을 낳음
var sub = beget(sup); // 핵심
// 강화
sub.attr1 = 1;
sub.attr2 = 2;
//sub.attr3...
alert(sub.val); // 1
alert(sub.arr); // 1
alert(sub.attr1); // 1
P.S. 어때요~ 보였나요, 자식을 낳는 함수 beget 이 나타났습니다.
2. 핵심
자식을 낳는 함수를 사용하여*'순결한'새로운 객체를 얻음 ('순결한'것은 인스턴스 속성이 없기 때문). 그 후 점차적으로 강화함 (인스턴스 속성을 채움)*
P.S. ES5 는 Object.create() 함수를 제공하며, 내부는 프로토타입식 상속입니다. IE9+ 가 지원.
3. 장단점
장점:
- 기존 객체에서 새로운 객체를 파생시킴. 커스텀 타입을 생성할 필요가 없음 (상속이라기보다는 객체 복제 같습니다..)
단점:
-
프로토타입 참조 속성은 모든 인스턴스가 공유함. 부모 클래스 객체 전체를 서브클래스의 프로토타입 객체로 사용하므로, 이 결함은 피할 수 없음.
-
코드 재사용을 구현할 수 없음 (새로운 객체는 즉시 얻고, 속성은 그 자리에서 추가함. 함수로 캡슐화되지 않았는데, 어떻게 재사용할 수 있나)
P.S. 이것과 상속이 큰 관계가 있을까요? 왜 니콜라스도 이것을 구현 상속의 한 방식으로 꼽았을까요? 큰 관계는 없지만, 일정한 관계는 있습니다.
육. 파라사이틱식
이 이름은 너무 이상합니다. 게다가*파라사이틱은 일종의 패턴 (套路)*이며, 상속 구현에만 사용할 수 있는 것은 아닙니다.
1. 구체적인 구현
function beget(obj){ // 자식을 낳는 함수 beget: 용은 용을 beget, 봉은 봉을 beget.
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
this.val = 1;
this.arr = [1];
}
function getSubObject(obj){
// 새로운 객체 생성
var clone = beget(obj); // 핵심
// 강화
clone.attr1 = 1;
clone.attr2 = 2;
//clone.attr3...
return clone;
}
var sub = getSubObject(new Super());
alert(sub.val); // 1
alert(sub.arr); // 1
alert(sub.attr1); // 1
2. 핵심
프로토타입식 상속에만 망토를 씌운 것뿐. 상속처럼 보이게 되었습니다 (위에서 소개한 프로토타입식 상속은 객체 복제 같습니다)
주의: beget 함수는 필수가 아닙니다. 즉, 새로운 객체 생성 -> 강화 -> 해당 객체 반환, 이러한 과정을 파라사이틱 상속이라고 합니다. 새로운 객체가 어떻게 생성되는지는 중요하지 않습니다 (beget 으로 낳은 것, new 로 생성한 것, 리터럴로 즉시 만든 것.. 모두 가능)
3. 장단점
장점:
- 여전히 커스텀 타입을 생성할 필요가 없음
단점:
- 함수 재사용을 구현할 수 없음 (프로토타입을 사용하지 않았으니, 당연히 불가)
P.S. 스토리 해석: 결함이 있는 파라사이틱 상속 + 완벽하지 않은 조합 상속 = 완벽한 파라사이틱 조합 상속, 돌아가 찾아 보세요. 어디에서 파라사이틱을 사용했는지.
칠. 6 가지 상속 방식의 연관성
P.S. 점선은 보조적 작용을 나타내고, 실선은 결정적 작용을 나타냅니다.
참고 자료
-
『JavaScript 高级程序设计』
-
『JavaScript 语言精粹』

아직 댓글이 없습니다