클래스와 new와 메서드와 this에서 만든 Counter나 StampCard와 거의 같지만, 한 번에 올라가는 숫자나 도장 개수만 다른 클래스를 하나 더 만들고 싶을 때가 있습니다. 처음부터 똑같은 코드를 다시 적는 대신 **extends(상속)**를 쓰면, 기존 클래스(부모 클래스)의 생성자와 메서드를 그대로 물려받으면서 달라지는 부분만 덧붙이거나 바꿀 수 있습니다.
1extends와 super — 부모 클래스를 물려받아 넓히기
기존 Counter 클래스를 물려받아, 한 번에 step만큼 숫자를 올리는 StepCounter 클래스를 만들어 보겠습니다.
class Counter {
constructor(start) {
this.count = start;
}
up() {
this.count += 1;
}
}
이어서 extends Counter로 자식 클래스 StepCounter를 정의합니다.
class StepCounter
extends Counter {
constructor(start, step) {
super(start);
this.step = step;
}
up() {
this.count += this.step;
}
}
const s = new StepCounter(0, 5);
s.up();
console.log(s.count);
5여기서 물려주는 Counter를 부모 클래스, 물려받는 StepCounter를 자식 클래스라고 부릅니다.
extends Counter:StepCounter가Counter를 물려받는다는 표시입니다.super(start): 자식 클래스에constructor를 적을 때는this를 쓰기 전에 반드시super(...)를 먼저 불러야 합니다.super(start)는 부모Counter의constructor(start)를 실행해 새 객체에this.count = start를 먼저 채워 줍니다. 그 뒤에야 자식 생성자에서this.step = step처럼this를 쓸 수 있습니다.
만약 자식 클래스에 constructor를 아예 적지 않으면, 자바스크립트가 받은 인자를 그대로 넘기는 super(...args)를 자동으로 불러 줍니다. 반대로 자식 constructor를 적으면서 super()를 빠뜨리면 ReferenceError가 납니다.
class Counter {
constructor(start) {
this.count = start;
}
}
class Bad extends Counter {
constructor(start, step) {
this.step = step;
}
}
const b = new Bad(0, 5);
main.js:8 this.step = step; ^ ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor at new Bad (main.js:8:5) at main.js:11:112메서드를 찾는 차례 — 객체 → 자식 클래스 → 부모 클래스
자식 클래스가 부모와 같은 이름의 메서드(up())를 다시 적는 것을 **메서드 재정의(오버라이딩)**라고 합니다. sc.up()이나 sc.reset()처럼 메서드를 부르면 자바스크립트는 아래 세 단계를 차례로 올라가며 처음 찾은 메서드를 실행합니다.
class Counter {
constructor(start) {
this.count = start;
}
up() {
this.count += 1;
}
reset() {
this.count = 0;
}
}
이어서 up()만 다시 정의한 StepCounter를 만듭니다.
class StepCounter
extends Counter {
constructor(start, step) {
super(start);
this.step = step;
}
up() {
this.count += this.step;
}
}
이제 인스턴스를 만들고 up()과 reset()을 차례로 부릅니다.
const s = new StepCounter(0, 5);
s.up();
console.log(s.count);
s.reset();
console.log(s.count);
5
0s.up()을 부를 때: 먼저s객체 안을 봅니다(데이터 속성count,step만 있음) → 자식 클래스StepCounter로 올라갑니다 → 여기에up()이 있으므로 바로 그up()을 실행하고 멈춥니다. 부모Counter의up()까지 올라가지 않습니다.s.reset()을 부를 때:s객체 안에도 없고 → 자식 클래스StepCounter에도reset이 없으므로 →extends연결을 따라 부모 클래스Counter로 올라가reset()을 찾아 실행합니다. 이때도 점 앞이s였으므로reset()안의this는s를 가리킵니다.
3instanceof와 비공개 필드
객체 instanceof 클래스 연산자는 그 객체가 해당 클래스나 그 자식 클래스로 만들어졌는지 확인해 true 또는 false를 돌려줍니다.
class Counter {}
class StepCounter
extends Counter {}
const sc = new StepCounter();
console.log(
sc instanceof StepCounter,
sc instanceof Counter,
);
true truesc는 StepCounter의 인스턴스이면서 동시에 부모인 Counter의 인스턴스이기도 하므로 둘 다 true가 나옵니다.
한편 클래스 안에서 속성 이름 앞에 #을 붙이면(#pin) 클래스 중괄호 안에서만 읽고 바꿀 수 있는 **비공개 필드(private field)**가 됩니다. 클래스 밖에서 읽으려고 하면 문법 오류가 나고, console.log로 객체를 찍어도 겉으로 드러나지 않습니다.
class Card {
#pin = 1234;
check(n) {
return this.#pin === n;
}
}
const c = new Card();
console.log(c, c.check(1234));
Card {} true예제
플로우 카페의 일반 StampCard(한 번에 도장 1개)를 물려받아, 한 번에 도장을 2개씩 찍어 주는 우수 고객용 VipCard 클래스를 만들어 보겠습니다.
class StampCard {
constructor(owner) {
this.owner = owner;
this.stamps = 0;
}
add() {
this.stamps += 1;
}
}
이어서 StampCard를 상속받는 VipCard를 만들고 준호의 카드를 찍어 봅니다.
class VipCard
extends StampCard {
add() {
this.stamps += 2;
}
}
const vip = new VipCard('준호');
vip.add();
console.log(vip.owner);
console.log(vip.stamps);
준호
2VipCard에는 constructor를 따로 적지 않았으므로 new VipCard('준호')를 부를 때 부모 StampCard의 생성자가 자동으로 불려 owner: '준호', stamps: 0을 채웁니다. 이어서 vip.add()를 부르면 VipCard에서 다시 정의한 add()가 실행되어 도장이 한 번에 2개 올라갑니다.
자주 틀리는 곳
1. 자식 constructor에서 super()를 빠뜨리거나 this 뒤에 둠
자식 클래스에 constructor를 적었다면, this.step = step처럼 this에 접근하기 전에 반드시 super(...)를 먼저 불러야 합니다. 빠뜨리거나 순서를 바꾸면 ReferenceError가 납니다.
2. 부모에 이미 있는 메서드를 자식마다 똑같이 다시 적음
자식 클래스에 적지 않은 메서드는 자동으로 부모 클래스에서 찾아 실행합니다. 달라지는 메서드만 자식 클래스에 적고, 그대로 쓸 메서드는 부모 클래스에 한 번만 둡니다.
3. instanceof가 부모 클래스에는 false라고 생각함
자식 클래스로 만든 인스턴스는 부모 클래스와도 extends로 이어져 있으므로 sc instanceof Counter도 true입니다.
4. # 비공개 필드를 클래스 밖에서 점으로 꺼내려 함
#pin처럼 #이 붙은 필드는 그 클래스 중괄호 안에서만 this.#pin으로 쓸 수 있습니다. 클래스 밖에서 c.#pin이라고 적으면 SyntaxError가 납니다.
확인 문제
문제 1
다음 코드를 실행했을 때 출력되는 두 줄을 순서대로 쓰세요.
class Box {
constructor(n) {
this.n = n;
}
add() {
this.n += 1;
}
show() {
return `값: ${this.n}`;
}
}
이어서 TenBox를 만들고 메서드를 부릅니다.
class TenBox extends Box {
add() {
this.n += 10;
}
}
const b = new TenBox(5);
b.add();
console.log(b.n);
console.log(b.show());
힌트
b.add()는 자식 클래스 TenBox에서 다시 정의한 add()를 실행하고, b.show()는 TenBox에 없으므로 부모 클래스 Box의 show()를 실행합니다.
정답
15
값: 15new TenBox(5)는 부모Box의 생성자로this.n = 5를 만듭니다.b.add()는TenBox의add()를 찾아5 + 10으로15를 만듭니다.b.show()는 부모Box의show()를 찾아'값: 15'를 돌려줍니다.
문제 2
Drink 클래스를 물려받는 Iced 클래스가 부모 생성자로 name을 먼저 채운 뒤 this.ice = ice를 저장하도록 빈칸 ____ 두 곳을 채우세요.
class Drink {
constructor(name) {
this.name = name;
}
}
이어서 자식 클래스 Iced를 정의합니다.
class Iced ____ Drink {
constructor(name, ice) {
____(name);
this.ice = ice;
}
}
const d =
new Iced('라테', '많이');
console.log(d.name, d.ice);
힌트
클래스를 상속받을 때는 extends를 쓰고, 자식 생성자 안에서 부모 생성자를 부를 때는 super(...)를 씁니다.
정답
- 첫째 빈칸:
extends - 둘째 빈칸:
super
class Drink {
constructor(name) {
this.name = name;
}
}class Iced extends Drink {
constructor(name, ice) {
super(name);
this.ice = ice;
}
}
const d =
new Iced('라테', '많이');
console.log(d.name, d.ice);라테 많이문제 3
아래 Coupon 클래스를 extends로 물려받는 BigCoupon 클래스를 직접 쓰세요. BigCoupon은 use(p)를 부를 때 this.off만 빼는 대신 this.off * 2를 뺀 값을 돌려주어야 합니다(constructor는 따로 적지 않아도 부모 것을 물려받습니다).
기대 출력과 시험 값은 다음과 같습니다.
3500
class Coupon {
constructor(off) {
this.off = off;
}
use(p) {
return p - this.off;
}
}
이어서 BigCoupon을 작성해 시험합니다.
// 여기에 BigCoupon을 쓰세요
const c = new BigCoupon(500);
console.log(c.use(4500));
힌트
class BigCoupon extends Coupon 안에 use(p) 메서드를 정의하고 return p - this.off * 2;를 적습니다.
정답
class Coupon {
constructor(off) {
this.off = off;
}
use(p) {
return p - this.off;
}
}class BigCoupon
extends Coupon {
use(p) {
return p - this.off * 2;
}
}
const c = new BigCoupon(500);
console.log(c.use(4500));3500컴퓨터에서 해 보기(선택) — 1절의 StepCounter 코드에서 super(start); 줄을 지우고 돌려 보세요. ReferenceError 오류가 나는 것을 확인한 뒤 다시 super(start);를 넣어 보세요.