[JavaScript] class - (2) 클래스 상속

경우·2022년 11월 17일
0

JavaScript

목록 보기
17/19

클래스 상속

클래스 상속을 사용하면 클래스를 다른 클래스로 확장할 수 있다. 기존에 존재하던 기능을 토대로 새로운 기능을 만들 수 있다는 것.

☑️ 'extends' 키워드

먼저, 클래스 Animal 생성

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

  run(speed) {
    this.speed = speed;
    console.log(`${this.name}은/는 속도 ${this.speed}로 달립니다.`);
  }

  stop() {
    this.speed = 0;
    console.log(`${this.name}이/가 멈췄습니다.`);
  }
}

const animal = new Animal("동물");

객체 animal과 클래스 Animal의 관계를 그림으로 나타내면 다음과 같다.또 다른 클래스 Rabbit 생성

토끼는 동물이므로 Rabbit은 동물 관련 메서드가 담긴 Animal을 확장해서 만들어야 한다. 이렇게 하면 토끼도 동물이 할 수 있는 '일반적인' 동작을 수행할 수 있다.

클래스 확장 문법 class Child extends Parent를 사용하여 Animal을 상속받는 class Rabbit을 생성해보자.

class Rabbit extends Animal {
  hide() {
    console.log(`${this.name}이/가 숨었습니다!`);
  }
}

const rabbit = new Rabbit("흰 토끼");

rabbit.run(5); // 흰 토끼은/는 속도 5로 달립니다.
rabbit.hide(); // 흰 토끼이/가 숨었습니다!

클래스 Rabbit을 사용해 만든 객체는 rabbit.hide() 같은 Rabbit에 정의된 메서드에도 접근할 수 있고, rabbit.run() 같은 Animal에 정의된 메서드에도 접근할 수 있다.

키워드 extends는 프로토타입을 기반으로 동작한다. extendsRabbit.prototype.[[Prototype]]Animal.prototype으로 설정한다. 그렇기 때문에 Rabbit.prototype에서 메서드를 찾지 못하면 Animal.prototype에서 메서드를 가져온다.

☑️ 메서드 오버라이딩

특별한 사항이 없으면 class Rabbitclass Animal에 있는 메서드를 그대로 상속받는다.

하지만 Rabbit에서 stop() 등의 메서드를 자체적으로 정의하면 상속받은 메서드가 아닌 자체 메서드가 사용된다.

class Rabbit extends Animal {
  stop() {
    // rabbit.stop()을 호출할 때
    // Animal의 stop()이 아닌, 이 메서드가 사용됩니다.
  }
}

부모 메서드 전체를 교체하지 않고 부모 메서드를 토대로 일부 기능만 변경하고 싶을 땐 커스텀 메서드를 만들어 작업하게 되는데, 이미 커스텀 메서드를 만들었더라도 변경 전 부모 메서드를 호출하고 싶은 상황이 있을 수 있다.

키워드 super는 이럴 때 사용한다.

  • super.method(...)는 부모 클래스에 정의된 메서드를 호출한다.
  • super(...)는 부모 생성자를 호출하는데, 자식 생성자 내부에서만 사용할 수 있다.
class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed = speed;
    console.log(`${this.name}은/는 속도 ${this.speed}로 달립니다.`);
  }

  stop() {
    this.speed = 0;
    console.log(`${this.name}이/가 멈췄습니다.`);
  }
}

class Rabbit extends Animal {
  hide() {
    console.log(`${this.name}이/가 숨었습니다!`);
  }

  stop() {
    super.stop(); // 부모 클래스의 stop()을 호출해 멈추고,
    this.hide(); // 숨는다.
  }
}

const rabbit = new Rabbit("흰 토끼");

rabbit.run(5); // 흰 토끼은/는 속도 5로 달립니다.
rabbit.stop(); // 흰 토끼이/가 멈췄습니다. 흰 토끼이/가 숨었습니다!

Rabbit은 이제 실행 중간에 부모 클래스에 정의된 메서드 super.stop()을 호출하는 stop()을 가지게 되었다.

☑️ 생성자 오버라이딩

지금까진 Rabbit에 자체 constructor가 없었다.

클래스가 다른 클래스를 상속받고 constructor가 없는 경우엔 아래처럼 부모 constructor를 호출하여 constructor가 만들어진다.

class Rabbit extends Animal {
  // 자체 생성자가 없는 클래스를 상속받으면 자동으로 만들어짐
  constructor(...args) {
    super(...args);
  }
}

Rabbit에 커스텀 생성자를 nameearLength를 추가해보자.

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

class Rabbit extends Animal {
  constructor(name, earLength) {
    this.speed = 0;
    this.name = name;
    this.earLength = earLength;
  }
  // ...
}

const rabbit = new Rabbit("흰 토끼", 10);

위 코드는 에러를 발생시키는데 에러는 다음과 같다.
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

즉, 상속 클래스의 생성자에선 반드시 this를 사용하기 전 super(...)를 호출해야 한다는 말

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

class Rabbit extends Animal {
  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }
  // ...
}

const rabbit = new Rabbit("흰 토끼", 10);
console.log(rabbit.name); // 흰 토끼
console.log(rabbit.earLength); // 10

exercise

매 초마다 시간을 출력해주는 클래스 Clock이 있습니다. Clock을 상속받는 ExtendedClock을 만들고, precision(정확도)이라는 매개변수도 추가해보세요. precision은 ‘초’ 사이의 간격을 의미하고, 기본값은 1000(1초)이 되어야 합니다.

class Clock {
  constructor({ template }) {
    this.template = template;
  }

  render() {
    let date = new Date();

    let hours = date.getHours();
    if (hours < 10) hours = "0" + hours;

    let mins = date.getMinutes();
    if (mins < 10) mins = "0" + mins;

    let secs = date.getSeconds();
    if (secs < 10) secs = "0" + secs;

    let output = this.template
      .replace("h", hours)
      .replace("m", mins)
      .replace("s", secs);

    console.log(output);
  }

  stop() {
    clearInterval(this.timer);
  }

  start() {
    this.render();
    this.timer = setInterval(() => this.render(), 1000);
  }
}

module.exports = Clock
const Clock = require("./Clock");

class ExtendedClock extends Clock {
  constructor(options) {
    super(options);
    let { precision = 1000 } = options;
    this.precision = precision;
  }

  start() {
    this.render();
    this.timer = setInterval(() => this.render(), this.precision);
  }
}

const extendedClock = new ExtendedClock({
  template: "h:m:s",
  precision: 1000,
});

extendedClock.start();

Reference

https://ko.javascript.info/

0개의 댓글