클래스 상속을 사용하면 클래스를 다른 클래스로 확장할 수 있다. 기존에 존재하던 기능을 토대로 새로운 기능을 만들 수 있다는 것.
먼저, 클래스 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