키보드의 키를 비유로 들면, 키보드의 키를 사용하는 방법은 '누른다'이다. 하지만 똑같은 동작 방법의 키라고 하더라도 ESC는 취소를 ENTER는 실행의 목적을 가지고 있다. 이처럼 다형성이란 동일한 조작 방법으로 동작시키지만, 동작 방법은 다른 것을 의미한다.
상속
1. 기본 개념
class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
run(speed) {
this.speed = speed;
alert(`${this.name} 은/는 속도 ${this.speed}로 달립니다.`);
}
stop() {
this.speed = 0;
alert(`${this.name} 이/가 멈췄습니다.`);
}
}
let animal = new Animal("동물");
class Rabbit extends Animal {
hide() {
alert(`${this.name} 이/가 숨었습니다!`);
}
}
let rabbit = new Rabbit("흰 토끼");
rabbit.run(5); // 흰 토끼 은/는 속도 5로 달립니다.
rabbit.hide(); // 흰 토끼 이/가 숨었습니다!
extends 키워드는 프로토타입을 기반으로 작동한다. extends는 Rabbit.prototype.[[Prototype]]을 Animal.prototype으로 설정한다. 그렇기 때문에 Rabbit.protype에서 메서드를 찾지 못하면, Animal.prototype에서 메서드를 가져온다.
<엔진이 메서드 rabbit.run의 존재를 찾는 과정>
객체 rabbit에 run이 있나 확인한다.
run이 없다.
rabbit의 프로토타입인 Rabbit.prototype에 메서드가 있나 확인한다.
hide만 있고, run은 없다.
extends를 통해 관계가 만들어진, Rabbit.protytpe의 프로토타입인, Animal.prototype에 메서드가 있는지 확인한다.
run을 찾고 실행한다.
2. 메서드 오버라이딩
특별한 상황이 없으면, class Rabbit은 class Animal을 '그대로' 상속받는다.
그런데, Rabbit에서 stop() 등의 메서드를 자체적으로 정의하면, 상속받은 메서드가 아닌 자체 메서드가 사용된다.
class Rabbit extends Animal {
stop() {
// rabbit.stop()을 호출할 때
// Animal의 stop()이 아닌, 이 메서드가 사용됩니다.
}
}
자식 메서드에서 커스텀 메서드를 만들었지만, 부모 메서드도 같이 호출하려고 할 때, super 키워드를 사용한다.
class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
run(speed) {
this.speed = speed;
alert(`${this.name}가 속도 ${this.speed}로 달립니다.`);
}
stop() {
this.speed = 0;
alert(`${this.name}가 멈췄습니다.`);
}
}
class Rabbit extends Animal {
hide() {
alert(`${this.name}가 숨었습니다!`);
}
stop() {
super.stop(); // 부모 클래스의 stop을 호출해 멈추고,
this.hide(); // 숨습니다.
}
}
let rabbit = new Rabbit("흰 토끼");
rabbit.run(5); // 흰 토끼가 속도 5로 달립니다.
rabbit.stop(); // 흰 토끼가 멈췄습니다. 흰 토끼가 숨었습니다!
3. 생성자 오버라이딩
class Rabbit extends Animal {
// 자체 생성자가 없는 클래스를 상속받으면 자동으로 만들어짐
constructor(...args) {
super(...args);
}
}
자체 생성자가 없는 클래스를 상속받으면 자동으로 만들어진다.
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;
}
// ...
}
// 동작하지 않습니다!
let rabbit = new Rabbit("흰 토끼", 10); // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
상속 클래스의 생성자에서는 this를 사용하기 전에 반드시 super(...)를 호출해야 한다.
이러한 이유는 상속 클래스의 생성자가 호출될 때의 과정 때문이다.
자바스크립트는 '상속 클래스의 생성자 함수(derived constructor)'와 그렇지 않은 생성자 함수를 구분한다. 상속 클래스의 생성자 함수엔 특수 내부 프로퍼티인 [[ConstructorKind]]:"derived"가 이름표처럼 붙는다.
일반 클래스의 생성자 함수와 상속 클래스의 생성자 함수 간 차이는 new와 함께 드러난다.
일반 클래스가 new와 함께 실행되면, 빈 객체가 만들어지고 this에 이 객체를 할당한다.
반면, 상속 클래스의 생성자 함수가 실행되면, 일반 클래스에서 일어난 일이 일어나지 않는다. 상속 클래스의 생성자 함수는 빈 객체를 만들고 this에 이 객체를 할당하는 일을 부모 클래스의 생성자가 처리해주길 기대한다.
이런 차이 때문에 상속 클래스의 생성자에선 super를 호출해 부모 생성자를 실행해 주어야 한다. 그렇지 않으면 this가 될 객체가 만들어지지 않아 에러가 발생한다.
상속, 다형성, 코드의 재사용성
다형성
상속
1. 기본 개념
extends
키워드는 프로토타입을 기반으로 작동한다.extends
는Rabbit.prototype.[[Prototype]]
을Animal.prototype
으로 설정한다. 그렇기 때문에Rabbit.protype
에서 메서드를 찾지 못하면,Animal.prototype
에서 메서드를 가져온다.<엔진이 메서드
rabbit.run
의 존재를 찾는 과정>rabbit
에run
이 있나 확인한다.run
이 없다.rabbit
의 프로토타입인Rabbit.prototype
에 메서드가 있나 확인한다.hide
만 있고,run
은 없다.extends
를 통해 관계가 만들어진,Rabbit.protytpe
의 프로토타입인,Animal.prototype
에 메서드가 있는지 확인한다.run
을 찾고 실행한다.2. 메서드 오버라이딩
특별한 상황이 없으면,
class Rabbit
은class Animal
을 '그대로' 상속받는다.그런데,
Rabbit
에서stop()
등의 메서드를 자체적으로 정의하면, 상속받은 메서드가 아닌 자체 메서드가 사용된다.자식 메서드에서 커스텀 메서드를 만들었지만, 부모 메서드도 같이 호출하려고 할 때,
super
키워드를 사용한다.3. 생성자 오버라이딩
상속 클래스의 생성자에서는
this
를 사용하기 전에 반드시super(...)
를 호출해야 한다.이러한 이유는 상속 클래스의 생성자가 호출될 때의 과정 때문이다.
자바스크립트는 '상속 클래스의 생성자 함수(derived constructor)'와 그렇지 않은 생성자 함수를 구분한다. 상속 클래스의 생성자 함수엔 특수 내부 프로퍼티인
[[ConstructorKind]]:"derived"
가 이름표처럼 붙는다.일반 클래스의 생성자 함수와 상속 클래스의 생성자 함수 간 차이는
new
와 함께 드러난다.new
와 함께 실행되면, 빈 객체가 만들어지고this
에 이 객체를 할당한다.this
에 이 객체를 할당하는 일을 부모 클래스의 생성자가 처리해주길 기대한다.이런 차이 때문에 상속 클래스의 생성자에선
super
를 호출해 부모 생성자를 실행해 주어야 한다. 그렇지 않으면this
가 될 객체가 만들어지지 않아 에러가 발생한다.