클래스 상속을 사용하면 클래스를 다른 클래스로 확장할 수 있다. 기존에 존재하던 기능을 토대로 새로운 기능을 만들 수 있다는 것.
먼저, 클래스 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는 프로토타입을 기반으로 동작한다. extends는 Rabbit.prototype.[[Prototype]]을 Animal.prototype으로 설정한다. 그렇기 때문에 Rabbit.prototype에서 메서드를 찾지 못하면 Animal.prototype에서 메서드를 가져온다.
특별한 사항이 없으면 class Rabbit은 class 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에 커스텀 생성자를 name과 earLength를 추가해보자.
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