
class를 이용하지 않고 prototype를 사용해 상속하는 방법에 대해서 알아보자.
아래 코드를 확인해보자.
class Person{
constructor(name, first, second){
this.name = name;
this.first = first;
this.second = second;
}
sum(){
return this.first + this.second;
}
}
class PersonPlus extends Person{
constructor(name, first, second, third){
super(name, first, second);
this.third = third;
}
sum(){
return super.sum() + this.third;
}
avg(){
return (this.first + this.second + this.third) / 3;
}
}
// 출력
var kim = new PersonPlus('kim',10,20,30);
console.log("kim.sum() : ", kim.sum());
console.log("kim.avg() : ", kim.avg());
Person이라는 클래스를 만들고 그 클래스가 new를 통해 생성될때 constructor 생성자가 실행되면서 생성하려는 객체의 초기값을 가지게 된다.
여기에 Person의 sum은 해당 객체에 소속된 것이 아닌 prototype에 소속되어 있다.
sum은 Person을 통해 생성되는 모든객체가 공유하는 함수(메소드)다 라고 생각하면 된다.
그리고 PersonPlus는 모든 기능을 다 구현하는게 아니라 Person의 기능을 물려받는다.
PersonPlus의 생성자를 보면 super(name, first, second);
즉 super메소드를 통해 부모클래스가 가지고 있는 constructor생성자를 실행하고
나머지는 자식클래스에서 별도로 생성자과정을 실행하게 된다.
즉 부모의 코드를 재활용 하면서 자식만의 작업을 할 수 있다.
자식클래스인 PersonPlus의 메소드 sum은 부모가 가지고 있는 super.sum()을 통해 재활용하고 자식만의 작업을 추가한다.
avg()는 부모에게 없는 메소드고 PersonPlus에 추가된 메소드이다.
call를 사용해 생성자를 상속해보자.
/** constructor-inheritance.js */
function Person(name, first, second){
this.name = name;
this.first = first;
this.second = second;
}
Person.prototype.sum = function(){
return this.first + this.second;
}
function PersonPlus(name, first, second, third){
Person.call(this,name,first,second);
this.third = third;
}
PersonPlus.prototype.avg = function(){
return (this.first + this.second + this.third)/3;
}
// 출력
var kim = new PersonPlus('kim',10,20,30);
console.log("kim.sum()", kim.sum());
console.log("kim.avg()", kim.avg());
Person.prototype.sum = function(){
return this.first + this.second;
}
이번에는 Person의 생성자를 상속한 생성자를 만들어보자
만약 아래와 같은 코드를 만든다면 비효율적일 것이다.
function PersonPlus(name, first, second, third){
this.name = name;
this.first = first;
this.second = second;
this.third = third;
}
위 코드는 이미 부모가 가지고 있는 코드인데 중복이 되어버린다.
function PersonPlus(name, first, second, third){
Person.call(this,name,first,second);
this.third = third;
}
Person의 call을 실행시켜 첫번째 인자로 PersonPlus라고 하는 생성자가 new를통해 만들어지는 객체 즉 this를 인자로 준다.
이전장에 보았던
class PersonPlus extends Person{
constructor(name, first, second, third){
super(name, first, second);
this.third = third;
...
}
와 같다고 생각하면 된다.
Uncaught TypeError: kim.sum is not a function
PersonPlus는 Person의 생성자를 호출하는데 성공했지만
PersonPlus는 sum이라고 하는 함수를 가지고 있지 않다.
PersonPlus는 부모를 호출한 것이지 부모와 아무런 관련이 없는 상태이다.
이로인해 kim.sum()이라는 메소드를 호출할때 PersonPlus는 sum이라는 메소드를 가지고 있지 않고 Person이 가지고 있지만 실행할 수 없다.
아래와 같은 상태라고 보면 된다.
부모 constructor 생성자 Person이 존재하며 prototype속성은 Person의 프로토타입 함수를 가리키며 서로 상호참조되고있는 상태이다.
자식으로 만들 PersonPlus도 자신의 프로토타입 객체를 가지고 있는 상태이다.
new연산자를 통해 생성된 kim의 객체를 확인해 보자
해당 객체의 __proto__ 속성이 자신을 생성한 생성자 함수의 prototype이 가르키고 있는 객체를 가르키게 된다.
이 상태에서 kim.avg() 라고 하는 메소드를 호출시 kim라는 객체에 avg라는 속성이 없으므로 __proto__를 따라서 PersonPlus의 프로토타입 객체에 avg메소드가 있는지 없는지 확인한다.
avg는 존재하므로 실행이 된다.
그런데 kim.sum() 를 실행시키면 마찬가지로 kim이라는 객체에 sum이라는 속성이 없으므로 __proto__를 따라 PersonPlus의 프로토타입 객체에서 확인해보지만 sum메소드는 없다.
이상태로는 자바스크립트가 kim라는 객체는 sum이라는 메소드를 가지고 있지 않다고 에러로 출력한다.
그러면 PersonPlus의 prototype에 찾는 프로퍼티가 없을 때 Person의 prototype객체를 확인하도록 연결해 줘야한다.
이제 코드를 통해 수정해보자.
/** constructor-inheritance.js */
function Person(name, first, second){
this.name = name;
this.first = first;
this.second = second;
}
Person.prototype.sum = function(){
return this.first + this.second;
}
function PersonPlus(name, first, second, third){
Person.call(this,name,first,second);
this.third = third;
}
/** 여기서 추가 */
PersonPlus.prototype.__proto__ = Person.prototype;
PersonPlus.prototype.avg = function(){
return (this.first + this.second + this.third)/3;
}
// 출력
var kim = new PersonPlus('kim',10,20,30);
console.log("kim.sum()", kim.sum());
console.log("kim.avg()", kim.avg());
결과
kim.sum() 30
kim.avg() 20
여기서 쓰인
PersonPlus.prototype.proto = Person.prototype;
이 코드에서 __proto__는 표준이 아니기 때문에 Object.create()를 사용하자.
/** constructor-inheritance.js */
function Person(name, first, second){
this.name = name;
this.first = first;
this.second = second;
}
Person.prototype.sum = function(){
return this.first + this.second;
}
function PersonPlus(name, first, second, third){
Person.call(this,name,first,second);
this.third = third;
}
/** 여기서 추가 */
// PersonPlus.prototype.__proto__ = Person.prototype;
PersonPlus.prototype = Object.create(Person.prototype);
PersonPlus.prototype.avg = function(){
return (this.first + this.second + this.third)/3;
}
// 출력
var kim = new PersonPlus('kim',10,20,30);
console.log("kim.sum()", kim.sum());
console.log("kim.avg()", kim.avg());
그런데 Object.create()를 보면 PersonPlus.prototype.__proto__ = Person.prototype;
서로 기능이 다르다.
Object.create는 Person.prototype 이라는 객체를 __proto__로 하는 새로운 객체가 만들어진다.
이걸 PersonPlus.prototype = Object.create(Person.prototype); 로 고쳐
Object.create()로 만들어진 새로운 객체는 __proto__가 Person을 가르키게 된다.
여기서 문제가 있는데 kim.constructor 즉 kim의 생성자 객체를 확인해보자.
console.log("kim.constructor : ", kim.constructor);
결과 ->
kim.constructor : ƒ Person(name, first, second){
this.name = name;
this.first = first;
this.second = second;
}
kim의 constructor는 Person이라고 하는 함수가 나오는걸 확인할 수 있다.
kim의 생성자는 PersonPlus인데 Person을 생성자로 가르키고 있다.
아래 코드에 PersonPlus.prototype.constructor = PersonPlus;를 추가하고 콘솔로 kim의 cosntructor를 확인해본다.
/** constructor-inheritance.js */
function Person(name, first, second){
this.name = name;
this.first = first;
this.second = second;
}
Person.prototype.sum = function(){
return this.first + this.second;
}
function PersonPlus(name, first, second, third){
Person.call(this,name,first,second);
this.third = third;
}
/** 여기서 추가 */
// PersonPlus.prototype.__proto__ = Person.prototype;
PersonPlus.prototype = Object.create(Person.prototype);
PersonPlus.prototype.constructor = PersonPlus;
PersonPlus.prototype.avg = function(){
return (this.first + this.second + this.third)/3;
}
// 출력
var kim = new PersonPlus('kim',10,20,30);
console.log("kim.sum()", kim.sum());
console.log("kim.avg()", kim.avg());
ƒ PersonPlus(name, first, second, third){
Person.call(this,name,first,second);
this.third = third;
PersonPlus의 생성자가 잡히는 것을 확인할 수 있다.