본문 바로가기
  • think normal
새로워지기/서른의 생활코딩

[생활코딩 따라가기] javascript 객체 지향 프로그래밍_14 생성자를 통한 상속

by 청춘만화 2019. 5. 1.

javascript 객체 지향 프로그래밍

생코 수업 Link : https://opentutorials.org/module/4047/24630

 

생성자를 통한 상속 - JavaScript 객체 지향 프로그래밍

수업소개 이 수업은 불필요하게 어렵습니다. 동일한 역할을 하면서도 훨씬 이해하기 쉬운 class 상속 수업을 보실 것을 권해봅니다. 물론 보셔도 됩니다. ㅎㅎ  강의1 강의2 코드 constructor-inheritance.js (변경사항) function Person(name, first, second){ this.name = name; this.first = first; this.second = second; } Person.prototype.sum

opentutorials.org

 

14. 생성자를 통한 상속

1. 시작점,

아래 코드를 보면 Person과 PersonPlus 두 함수 객체를 확인할 수 있다. 코드 내용을 보면 Person과 PersonPlus 두 함수 객체는 인자의 개수 빼고는 동일한 코드로 구성되어 있다. 이런 경우 어떻게 하면 코드를 더 효율적으로 작성할 수 있을까?

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){
    //super(name, first, second);
    this.name = name;
    this.first = first;
    this.second = second
    this.third = third;
}
PersonPlus.prototype.arg=function(){
    return (this.first+this.second+this.third)/3;
}

var kim = new PersonPlus('kim',10,20,30);
//console.log("kim 합계 : ", kim.sum());
console.log("kim 평균 : ", kim.arg());

 

2. 객체와 객체 간 상속  프로퍼티 공유?, call() 사용 예제 

이전 포스트 12. 객체와 함수 부분을 참고하면 힌트를 얻을 수 있다. 물론 class형태로 작성해서 extends 하는 방법도 있으나 javascript 고유의 기능에 대한 학습용 예제라 할 수 있겠다. 다시 말해 함수기반 객체 방식으로 해결방안을 모색해보자는 것이다.

앞서, call()은 필요한 객체를 인자로 넣어 this를 지정할 수 있다고 했다.    <- 도구 함수 객체. call(사용자 함수 객체)  

이번 예제는 함수 객체 내부에서 call()를 사용하고 있다. 오호! 그래서,       <- 도구 함수 객체. call(this)로 작성되었다.

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(name, first, second); // 그냥 평범한 함수
    
    Person.call(this, name, first, second);  // this를 초기 인자로 던진다. + 필요한 인자
    // = super(name,first,second) 역할 수행 
    
    this.third = third;
}
PersonPlus.prototype.arg=function(){
    return (this.first+this.second+this.third)/3;
}

var kim = new PersonPlus('kim',10,20,30);
//console.log("kim 합계 : ", kim.sum());
console.log("kim 평균 : ", kim.arg());

출력 결과)

여기까지 평균을 구할 수 있었다. PersonPlus 함수 객체가 Person 함수 객체의 프로퍼티(값)를 상속받아 사용할 수 있었지만 Person 함수 객체의 sum() 메서드를 아직 사용하지 못하고 있기 때문에 합계를 구하고 있지는 못하고 있어 주석 처리되어 있는 상태이다.

 

3. sum 출력하기, 객체로 객체를 상속하기( 방법 1. __proto__ )

call()은 상속은 아니기 때문에 PersonPlus가 Person의 sum()에 접근할 수 없었다. 자, 이번엔 상속을 통해 Person과 PersonPlus 연결해보자. 아래 예제는 자바스크립트에서 객체가 객체를 상속하는 방법으로  __proto__ 를 사용하고 있다.

다만, 아래 코드를 잘 보면 PersonPlus.__proto__=Person; 하지 않고 PersonPlus.prototype.__proto__=Person.prototype; 하고 있다. 앞서서 PersonPlus 함수가 Person.call()을 해서 임시로 Person객체 프로퍼티를 사용할 수 있었기 때문에 모두 상속받을 필요가 없고 Person.prototype.sum으로 생성된 메서드만 PersonPlus.prototype에 상속받기 위함이다. 요컨대 필요한 것만 가져오기 위함이다.

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(name, first, second); // 그냥 평범한 함수, 앞에 new 도 없다 
    Person.call(this, name, first, second);  // = super(name,first,second) 역할 수행 
    this.third = third;
}
PersonPlus.prototype.__proto__=Person.prototype; // link, 단 비 표준이다. 
PersonPlus.prototype.arg=function(){
    return (this.first+this.second+this.third)/3;
}

var kim = new PersonPlus('kim',10,20,30);
console.log("kim 합계 : ", kim.sum());
console.log("kim 평균 : ", kim.arg());

출력 결과)

 

4. sum 출력하기, 객체로 객체를 상속하기( 방법 2. __proto__ 를 '정규 형태'로 개선)

표준 권고안인 Object.create()를 사용해보자. 아래 예제는 상속받는 객체. prototype=Object.create(상속해주는 객체. prototype)로 작성하고 있다. 

 

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.arg=function(){
    return (this.first+this.second+this.third)/3;
}

var kim = new PersonPlus('kim',10,20,30);
console.log("kim 합계 : ", kim.sum());
console.log("kim 평균 : ", kim.arg());

출력 결과) 결과는 __proto__와 동일하게 출력되지만 이슈가 한 가지 있다. 

 

5. 하지만 

Object.create()는 객체를 새로 만들어 버리는 생성자이다. 다시 말해 해당 코드는 Person.prototype를 새로운 객체로 만든 것이다. 그리고 그것을 PersonPlus.prototype에 덮어쓴 것이다. 따라서 PersonPlus.prototype.arg() 함수는 반드시 Object.create() 아래 작성되어야 한다. (반면, __proto__는 지정한 속성만 상속받은 것이다.)

그리고 아래 코드와 같이 PersonPlus의 constructor를 출력해보면 PersonPlus가 아닌, Person 객체가 출력되는 것을 알 수 있다. 

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 = Object.create(Person.prototype); //새로운 객체를 생성해서 연결 

PersonPlus.prototype.arg=function(){
    return (this.first+this.second+this.third)/3;
}

var kim = new PersonPlus('kim',10,20,30);
console.log("kim 합계 : ", kim.sum());
console.log("kim 평균 : ", kim.arg());

//그런데.. 만약, 
console.log("kim constructor : ", kim.constructor);

출력 결과)

이 또한, PersonPlus.prototype=Object.create(Person.prototype) 하는 과정에서 Person.prototype가 PersonPlus.prototype에 그대로 덮어 쓰였기 때문이다

 

6. 대안

요컨대 Person의 prototype객체에 기록된 sum()를 사용하기 위해 복사해봤더니, Person의 prototype객체 안에 있는 constructor가 같이 딸려 온 것이다. 

때문에 PersonPlus의 prototype에 있는 constructor에 원래의 PersonPlus 함수 객체를 다시 한번 덮어쓸 필요가 있었다. 어쩌면 임시방편처럼 보이는 이러한 방식은 앞서 포스팅한 13.prototype과 __proto__ 내용을 통해 이해해 볼 수 있다. 

다시 정리해보면, 

함수 객체 A를 하나 만들면 그 객체에 대한 A's prototype객체가 자동으로 생성되고 이들은 프로퍼티_prototype와 constructor를 통해 서로를 참조하고 있다. 다시 말해 생성된 함수 객체는 프로퍼티_prototype를 가지고 있고, 태초에? 이 함수 객체와 쌍으로 자동 생성된 이 함수 객체의 prototype객체는 constructor를 가지고 있다.

우리가 작성한 예제에서 함수 객체 A는 이름, 숫자와 같은 프로퍼티만 기록했고 A's prototype객체에는 프로퍼티_prototype를 사용하여 sum()이나 arg()와 같은 메서드를 기록하고 있다. 

이러한 상황 속에 함수 객체 A는 함수 객체 B를 call()해서 사용하고 있다.

그리고 함수 객체 A는 함수 객체 B의 prototype에 있는 sum()를 사용하기 위해 함수 객체 A는 함수 객체 B의 prototype를 복사하여 자신의 prototype에 덮어썼다. 자, 여기서 앞서 자동 생성된 이 함수 객체의 prototype객체는 constructor라고 했었다. 이 때문에 위 예제와 같은 이슈가 발생하게 된 것이다.

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 = Object.create(Person.prototype); 
PersonPlus.prototype.constructor = PersonPlus;

PersonPlus.prototype.arg=function(){
    return (this.first+this.second+this.third)/3;
}

var kim = new PersonPlus('kim',10,20,30);
console.log("kim 합계 : ", kim.sum());
console.log("kim 평균 : ", kim.arg());

// 정상 출력 
console.log("kim constructor : ", kim.constructor);

출력 결과)

 

 

7. 끝을 맺기 전에 

코드를 작성하면 위와 같이 처음부터 직접 작성하는 경우도 있지만 많은 사람이 협업하거나 이미 있는 코드에 새로운 기능을 추가하는 상황이 발생하기도 한다. 이럴 경우, 우리는 브라우저가 제공하는 개발자 모드를 통해 특정 객체의 constructor를 알아낼 수 있는 방법이 있다 고 egoing이 안내해주고 있다. 

1. 브라우저에서 새 창을 OPEN
2. 오른쪽 마우스 클릭, 검사 클릭
3. 암튼 불필요한 내용을 지우기 위해, 아래 Console 영역에 있는 ⊘ 아이콘 클릭해서 Clear
4. 기본으로 제공되고 있는 Date 객체를 통해 테스트 예제

console에 작성한 코드)

var d = new Date();
Date.prototype.constructor === Date 
Date === d.constructor 

 

위의 사례를 응용해서, 누군가가 만든 ABC객체를 사용하게 되는 경우 이 객체가 어떤 공장(함수객체 또는 class)에서 만들어 졌는지, 그 공장에는 어떤 장비들이 있고 사용할 수 있는지를 알아 볼 수 있다. 

끄읏~

 

8. 끝을 맺으며 

앞서 예제를 봐서 알겠지만 프로토타입 오리엔트 자바스크립트 함수 객체를 사용하는 경우 여러 복잡한? 이슈들을 마주 할 수 있다. 때문에 객체를 사용하는 경우 class를 사용하는 편이 협업 또는 자신의 유지보수에 도움이 될 수 있다. 다만 자바스크립트의 핵심 운영 원리인 프로토타입 오리엔트에 대한 개념과 이해가 있어야 초급에서 중급으로 성장할 수 있다고 egoing은 말하고 있다. 

개인적으로 자바스크립트의 핵심 운영 원리인 프로토타입 오리엔트에 대한 개념을 설명하고 있는 13.prototype과 __proto__ 내용을 학습하면서 과거?에 초급 JAVA를 배울때 JVM 구조가 오버랩되었다. JVM 구조를 그려보면 객체가 생성되고 호출, 참조, 실행되는 과정을 이해할 수 있는데, 지난 13.prototype과 __proto__ 내용도 마치 자바스크립트의 기본 구조를 설명하고 있는 느낌이었다.

물론 prototype과 __proto__에 대해 학습한 내용은 생활코딩의 취지에 따라 요약에 요약을 한 아주 기본적인 맛보기 내용일 것이다. 언감생심, 모두 이해한 것은 아니라고 생각한다. 하지만 이 맛보기 학습 덕분에 자바스크립트의 저 깊은 원천적인 곳들이 궁금해지기 시작했다. 두근두근 언젠가 저 깊은 곳을 항해할 날을 기약하며 이번 생활 코딩은 여기에서 끝을 맺기로 한다.

진짜 끄읏~

 

 

 

참고 Link_ JVM과 메모리 구조 http://www.libqa.com/wiki/76
 

Social Q&A LibQA™

libqa 는 글라이더 오픈소스팀이 만든 소셜 기반의 Q&A 지식공유 Wiki 플랫폼입니다.

www.libqa.com

참고 Link_ Java의 정석 제 6 장 객체지향개념 https://slidesplayer.org/slide/15925263/
 

Java의 정석 제 6 장 객체지향개념 I-2 Java 정석 남궁성 강의 - ppt download

객체지향개념 I-1 객체지향개념 I-2 객체지향개념 I-3 Java 1. 객체지향언어란? 2. 클래스와 객체 의 정석 Chapter 6. 객체지향개념 I 1. 객체지향언어란? 객체지향개념 I-1 2. 클래스와 객체 3. 변수와 메서드 객체지향개념 I-2 4. 메서드 오버로딩 제6장 객체지향개념1의 두번째 강의입니다. 변수와 메서드 그리고 메서드 오버로딩에 대해서 강의하겠습니다. 5. 생성자 객체지향개념 I-3 6. 변수의 초기화

slidesplayer.org

 

댓글