Frontend/JS.info 정리

class - class extends (상속)

Creative_Lee 2022. 6. 21. 17:38

1. 클래스 상속 ( extends )

클래스 상속을 사용하면 클래스를 다른 클래스로 확장할 수 있습니다.

extends 키워드로 상속을 구현할 수 있습니다.

class Animal {
  constructor(name){
    this.name = name;
    this.speed = 0;
  }

  run(speed){
    this.speed = speed;
    console.log(`${this.name} 달리는 중 속도 : ${this.speed}`)
  }  
}

class Human extends Animal {
  think(){
    console.log(`${this.name} 생각하는 중`)
  }
}

let human = new Human('Bob')

human.run(100) // Bob 달리는 중 속도 : 100
human.think() // Bob 생각하는 중


console.log(human.__proto__ === Human.prototype) // true
console.log(Human.prototype.__proto__ === Animal.prototype) // true

human 인스턴스는 Human.prototype을 상속 받았고,

Human.prototype은  Animal prototype를 상속 받았습니다.

 

위와 같은 prototype chain 덕분에

Human class 로 생성된 인스턴스에서

Animal class에 정의된 메소드를 사용할 수 있습니다.


extends 뒤에는 표현식이 올 수도 있습니다.

function func(msg) {
  return class {
    sayHi() { 
      console.log(msg)
    }
  }
}

class User extends func("Hello") {}

new User().sayHi(); // Hello

위와 같은 방법은 조건에 따라 다른 class를 상속 받게 할때 유용합니다.


2. super를 통한 메서드 오버라이딩

상속을 구현 함에 있어서 특별한 행동을 하지 않으면,

부모 class의 메소드를 그대로 상속 받습니다.

만약 부모 class의 메소드와 같은 이름의 메서드를 정의하면, 자식 class의 메소드가 사용됩니다.

 

또한 부모 메소드의 일부 기능만 수정하고 싶을땐 

super 키워드를 사용하면 됩니다.

super.method( ) 는 부모class에 정의 된 메서드, method 를 호출합니다.

class Animal {
  constructor(name){
    this.name = name;
    this.speed = 0;
  }

  run(speed){
    this.speed = speed;
    console.log(`${this.name} 달리는 중 속도 : ${this.speed}`)
  }  
}

class Human extends Animal {
  think(){
    console.log('생각도 하는 중')
  }

  run(){
    super.run()
    this.think()
  }
}

let human = new Human('Bob')

human.run()
// Bob 달리는 중 속도 : undefined
// 생각도 하는 중

화살표 함수에는 super 가 없습니다.

class Human extends Animal {
  run(speed){
    setTimeout(() => super.run(speed), 1000)
  }
}

let human = new Human('Bob')

human.run(10) // Bob 달리는 중 속도 : 10

화살표 함수에서 super 키워드를 사용하면 외부 함수에서 super 를 가져옵니다.

위 코드의 동작은 run( ) 메소드의 컨텍스트에서 사용한 super와 같습니다. 

class Human extends Animal {
  run(speed){
    setTimeout(function(){super.run(speed)}, 1000)
  }
}

let human = new Human('Bob')

human.run(10) // error

일반 함수를 사용하면 error가 발생합니다.


3. super를 통한 생성자 오버라이딩

부모 클래스를 상속 받을 때 자식 클래스에서  constructor 생성자를 명시하지 않으면,

다음과 같이 비어있는 constructor가 생성됩니다.

class Human extends Animal {
  // 보이지 않지만 생성됩니다.
  constructor(...args){
    super(...args);
  }
}

super( ... ) 는 자식 클래스의 constructor 내에서만 사용할 수 있는데,

부모 클래스의 constructor를 호출합니다.

 

위와 같은 동작 방식 덕분에

자식 생성자를 명시하지 않으면 모든 인수를 자동으로 부모 생성자에게 전달합니다.


이를 바탕으로 생성자를 오버라이딩 할 수 있습니다.

class Animal {
  constructor(name){
    this.name = name;
    this.speed = 0;
  }
}

class Human extends Animal { 
  constructor(name, sex){
    super(name);
    this.sex = sex 
  }
}

let human = new Human('Bob', 'male')

console.log(human) // Human {name: 'Bob', speed: 0, sex: 'male'}

super 키워드를 사용하지 않고 오버라이딩 하면 어떻게 될까요 ?

class Human extends Animal { 
  constructor(name, sex){
    this.name = name;
    this.sex = sex 
  }
} 

let human = new Human('Bob', 'male')

// Uncaught ReferenceError: 
// Must call super constructor in derived class before accessing 'this'  
// or returning from derived constructor

"상속 클래스의 생성자에서 this를 사용하기 전에 반드시 super( ) 를 호출해야 합니다."

라는 에러가 발생합니다.

 

에러의 발생 원인은 다음과 같은 자바스크립트의 동작방식 때문입니다.

 

1. 자바스크립트는 상속 클래스의 생성자 함수와 일반 클래스의 생성자 함수를 구분합니다.

 

2. 일반 클래스가 new와 함께 실행되면 빈 객체를 만들고 this에 빈 객체를 할당합니다.

    이후 constructor에 정의한 프로퍼티를 추가하고 리턴 합니다.

 

3. 상속 클래스의 동작에선 2번과 같은 동작이 일어나지 않습니다.

    상속 클래스의 constructor는 2번과 같은 동작을 부모 클래스에 위임합니다

 

정리하면, 상속 클래스의 this가 될 객체가 만들어지지 않아서 발생하는 에러입니다.

 

이러한 이유로

상속 클래스의 constructor 에서 super( ) 를 통해

부모 클래스의 constructor 를 실행해 주어야 합니다. 


4. 클래스 필드 오버라이딩

코드부터 보겠습니다.

class Animal {
  name = 'Animal'
  constructor(){
    console.log(this.name)
  }
}

class Human extends Animal { 
  name = 'Human'
} 

let animal = new Animal() // Animal
let human = new Human() // Animal

자식 클래스 Human에서 constructor를 명시 하지 않았으므로

부모 클래스 Animal의 constructor가 호출됩니다.

자식 클래스에서 클래스 필드를 오버라이딩 했음에도 log에는 둘 다 Animal이 출력됐습니다.

 

부모 생성자는 항상 부모 클래스 필드 값을 사용하기 때문입니다.

왜 그럴까요 ?

 

클래스 필드의 초기화 규칙이 정해져 있기 때문입니다.

자바스크립트 클래스 필드의 초기화 순서는 다음과 같이 규칙에 따라 달라집니다.

  • 아무것도 상속 받지 않는 베이스 클래스는 constructor 실행 이전에 초기화 됩니다.
  • 부모 클래스가 존재하는 경우, super( ) 실행 직후에 초기화 됩니다.

위 규칙에 따라

부모 클래스 Animal은 생성자 함수 실행 이전에 name = 'Animal' 클래스 필드가 초기화 됩니다.

자식 클래스 Human은 부모 클래스 Animal의 생성자 함수 실행 직후에 name = 'Human' 클래스 필드가 초기화 됩니다.

 

즉 자식 클래스의 클래스 필드에 name 은 아직 존재 하지 않기 때문에, ( 초기화 x )

부모 클래스의 클래스 필드에 이미 존재하는 name이 사용되는 것입니다. ( 초기화 o )

 

이러한 문제는 오버라이딩한 클래스 필드를 부모 생성자에서 사용할 때만 발생합니다.

위와 같은 문제들은 클래스 필드 대신 메서드를 사용하거나  getter, setter를 통해 해결할 수 있습니다.

 

 

OOP의 세계로

'Frontend > JS.info 정리' 카테고리의 다른 글

class - private, protected  (0) 2022.06.22
class - static method, property  (0) 2022.06.22
class - class 기본 문법  (0) 2022.06.20
Prototype - 프로토타입 메서드  (0) 2022.06.18
Prototype - 내장 객체의 프로토타입  (0) 2022.06.18