본문 바로가기

FrontEnd/Javascript

Class, Class에서의 상속과 생성자 호출

참고: https://www.youtube.com/watch?v=OpvtD7ELMQo&list=PLZKTXPmaJk8JZ2NAC538UzhY_UNqMdZB4&index=15

//생성자 함수에 의한 객체 생성 예

// 예1.
const User=function(name, age){
    this.age=age;
    this.name=name;
    this.showName=function(){
        console.log(this.name);
    }
}

let joe=new User("Joe", 30);
console.dir(joe);

// 예2
function User2(name,age) {
    this.name = name;
    this.age=age;
    this.showName=function(){
        console.log(this.name);
    }
  }
  
  var jason = new User2('Jason,22');
console.dir(jason);
//아래와 같이 클래스 안에 정의한 함수는 해당클래스(User)의 프로토타입 안에 저장됨. 
//이와는 달리 위의 예1,2 처럼 함수를 통해 정의된 생성자 함수
//는 그 안에 프라퍼티와 함수를 모두 가지고 있어 객체를 만들면(John, tom)그 안에 
//프라퍼티와 메서드가 있는 것을 확인할 수 있다(브라우저에서 확인가능).
class User3{
    constructor(name, age){
        this.name=name;
        this.age=age;
    }
    showName(){
        console.log(this.name);
    }
}

const tom=new User3("Tom",32);
console.dir(tom);

위 코드 실행결과 (클래스로 객체를 생성한 경우에만 해당 객체에 showName()메서드가 없는 것을 확인할 수 있다. 클래스로 객체를 생성할 경우 해당 객체의 프로토타입 객체에 메서드가 만들어지기 때문이다)

 

class Car{
    constructor(color){
        this.color=color;
        this.wheel=4;
    }
    drive(){
        console.log("drive");
    }
    stop(){
        console.log("stop");
    }
}

class Bmw extends Car{
    park(){
        console.log("park");
    }
}

const vehicle=new Bmw("Red");
console.dir(vehicle);

위코드의 결과. Bmw객체의 프로토입은 Bmw.prototype이고 그 위에 Car.prototype이 있고 또 다시 그 위에 Object.prototype이 존재한다.(Bmw.prototype이라고 따로 명시되어 있지 않지만 있다고 생각하고 보자)

 

위와같이 생성자 함수를 통해서도 객체를 생성할 수 있는데 왜 굳이 class를 만들어서 객체를 생성할까?

1. 생성자함수를 이용하여 객체를 생성할 때 new 키워드를 생략해도 오류없이 작동한다. 다만 해당변수에는 undefined값이 저장될 뿐이다(생성자함수에는 반환값이 없으므로). 즉, 개발자가 오류를 알아차릴 수 없다. 하지만 class객체를 생성할 경우 new키워드를 뺀다면 실행자체가 되질 않는다. 즉 오류를 분별할 수 있다는 것이다. 


for in 문을 사용하면 해당객체를 포함하여 프로토타입에 포함된 프라퍼티, 메서드를 모두 보여줌. 만약 해당 객체만 가지고 있는 프라퍼티를 보려면 hasOwnProperty메서드를 사용해야함. 반면, 클래스의 메서드는 for in 문에서 제외됨. 

for(const p in jason){
    console.log(p);
}
console.log("======================")
for(const p in tom){
    console.log(p);
}

 

생성자함수로 생성된 객체는 프로토타입을 통해 상속을 구현하지만 클래스로 생성된 객체는 extends를 통해서 상속을 구현함.

class Car{
    constructor(color){
        this.color=color;
        this.wheel=4;
    }
    drive(){
        console.log("drive");
    }
    stop(){
        console.log("stop");
    }
}

class Bmw extends Car{
    park(){
        console.log("park");
    }
}

const vehicle=new Bmw("Red");
console.dir(vehicle);

 

자바에서의 부모, 자식 생성자: 자식생성자에서 부모의 기본 생성자를 자동으로 호출하여 주지만 기본생성자가 아닌 경우 호출이 자동적으로 이루어지지 않는다. 따라서 명시적으로 부모 생성자 super(~~)를 코딩해 주어야 한다. 따라서 경우에 따라 자식생성자를 구현할때 super를 쓰는 경우도 있고 쓰지 않는 경우도 있다
https://blog.naver.com/heartflow89/220961980579 )

Java와는 구분되는 JS에서의 부모, 자식 생성자: JS에서 class의 constructor는 빈객체({ })를 만들고 this키워드로 그 빈객체를 가리키게 됨. 반면 자식클래스에 있는 constructor는 빈객체를 만드는 작업을 건너뜀. 따라서 자바와는 다르게 기본 생성자라 하더라도 반드시 자식 constructor 안에 super( ); 를 넣어 줘서 부모 클래스의 constructor를 실행해 주어야 함. (아예 constructor가 없는 경우도 있음. 아래에서 다룸)

class Car{
    constructor(color){
        this.color=color;
        this.wheels=4;
    }
    drive(){
        console.log("drive...");
    }
    stop(){
        console.log("stop...");
    }
}
class Bmw extends Car{
    constructor(){
        this.navigation=1;
    }
    park(){
        console.log("parking...");
    }
}

let car=new Bmw();//오류남. ReferenceError: Must call super constructor
//in derived class before accessing 'this' or returning from derived constructor
//  at new Bmw

 

반드시 자식의 constructor안에 super(); 키워드를 넣어 빈객체를 생성해 주어야 한다. 그래야 그 안에 멤버변수와 그 초기값을 넣을 수 있음


JS도 JAVA처럼 자식에서 생성자를 정의하지 않으면 기본생성자가 호출 되면서 부모의 기본 생성자를 또 호출한다. 그래서 아래와 같이 Bmw에 constructor를 정의하지 않으면 오류는 나지 않는다. 다만 정확한 값을 얻을 수 없다. 하지만 내가 인위적으로 자식에 어떤 constructor를 정의해 주면 기본 생성자는 더 이상 호출되지 않는다.

JS의 기본생성자

따라서 자식에 constructor를 정의해 준다면 반드시 적절한 형태의 super();를 코딩해 주어야 한다. 현식적으로 이렇게 세세히 고민하고 생각하기 에는 시간, 에너지가 없다. 그냥 class를 사용한 JS에서의 상속에서는 constructor를 정의해 주고 그 안에서 적절한 super를 호출해 준다고 기억하는 게 현실적인 해결책이다.)