JS 6.2 · 6부. 객체지향과 모던 JS

상속과 확장

extends 로 물려받고 super 로 부모를 호출합니다.

예상 학습 시간 15분 실행 단추가 있는 예제는 고쳐서 실행해 볼 수 있습니다 난이도 중급
개념 설명

이미 있는 것을 물려받습니다

할인 상품은 상품이면서 할인율이 더 있는 것입니다. 상품 클래스를 그대로 다시 적는 대신 물려받아 필요한 것만 더합니다.

  • extends — 물려받을 클래스를 적습니다.
  • super(...) — 부모의 constructor 를 부릅니다.
  • super.메서드() — 부모의 메서드를 부릅니다.
최소 예제 · 편집 가능

물려받아 더하기

script.js
class Product {
	constructor(name, price) {
		this.name = name;
		this.price = price;
	}

	describe() {
		return `${this.name} ${this.price}원`;
	}
}

class SaleProduct extends Product {
	constructor(name, price, percent) {
		super(name, price);
		this.percent = percent;
	}

	get salePrice() {
		return Math.floor(this.price * (100 - this.percent) / 100);
	}
}

const item = new SaleProduct("공책", 2000, 10);

console.log(item.name);
console.log(item.describe());
console.log(item.salePrice);
출력
공책 공책 2000원 1800

namedescribe 를 적지 않았는데 그대로 사용합니다. 부모에 있는 것은 자식에도 있습니다.

상세 사용법

같은 이름으로 다르게

부모와 같은 이름의 메서드를 두면 자식 것이 쓰입니다. 부모 것도 함께 쓰려면 super 로 부릅니다.

script.js
class Product {
	constructor(name, price) {
		this.name = name;
		this.price = price;
	}

	describe() {
		return `${this.name} ${this.price}원`;
	}
}

class SaleProduct extends Product {
	constructor(name, price, percent) {
		super(name, price);
		this.percent = percent;
	}

	describe() {
		return `${super.describe()} → ${this.percent}% 할인`;
	}
}

const plain = new Product("연필", 1000);
const sale = new SaleProduct("공책", 2000, 10);

console.log(plain.describe());
console.log(sale.describe());
출력
연필 1000원 공책 2000원 → 10% 할인
상세 사용법

같은 이름으로 함께 다루기

부모가 같으면 한 배열에 담아 같은 이름으로 부를 수 있습니다. 각자 자기 것으로 움직입니다.

script.js
class Product {
	constructor(name) { this.name = name; }
	label() { return this.name; }
}

class SaleProduct extends Product {
	label() { return `[할인] ${this.name}`; }
}

class NewProduct extends Product {
	label() { return `[신상] ${this.name}`; }
}

const items = [
	new Product("연필"),
	new SaleProduct("공책"),
	new NewProduct("지우개")
];

items.forEach(item => console.log(item.label()));
출력
연필 [할인] 공책 [신상] 지우개

부르는 쪽은 어느 클래스인지 몰라도 됩니다. 새 종류를 더해도 이 코드는 그대로입니다.

상세 사용법

어느 클래스인지 확인하기

script.js
class Product {}
class SaleProduct extends Product {}

const sale = new SaleProduct();

console.log(sale instanceof SaleProduct);
console.log(sale instanceof Product);
console.log(new Product() instanceof SaleProduct);
console.log(sale.constructor.name);
출력
true true false SaleProduct

자식은 부모이기도 합니다. 반대는 아닙니다.

흔한 실수

처음에 자주 걸리는 것

1. super 를 빠뜨립니다

자식에 constructor 를 두었으면 this 를 쓰기 전에 super() 를 먼저 불러야 합니다.

ReferenceError: Must call super constructor in derived class before accessing 'this'
2. 너무 깊게 물려받습니다

세 단계를 넘어가면 어느 메서드가 어디에서 왔는지 알기 어려워집니다. 대개는 물려받는 대신 필요한 것을 넘겨받는 편이 낫습니다(2.6).

3. 부모의 감춘 필드를 자식에서 읽습니다

# 을 붙인 필드는 그 클래스 안에서만 보입니다. 자식도 밖입니다. 자식에게 열어 주려면 get 을 두세요(6.1).

실습 문제

직접 해보기

1. 정회원 만들기 난이도 하

이름을 받는 Member 와, 등급을 더 받는 PremiumMember 를 만드세요. 인사말은 각각 "홍길동님""홍길동님 (골드)" 입니다.

class Member { constructor(name) { this.name = name; } greet() { return `${this.name}님`; } } class PremiumMember extends Member { constructor(name, grade) { super(name); this.grade = grade; } greet() { return `${super.greet()} (${this.grade})`; } } console.log(new Member("김철수").greet()); console.log(new PremiumMember("홍길동", "골드").greet());
2. 도형 넓이 난이도 중

Shape 를 물려받는 사각형과 원을 만들고, 배열에 담아 넓이를 차례로 출력하세요. 원주율은 Math.PI 입니다.

class Shape { area() { return 0; } } class Rect extends Shape { constructor(w, h) { super(); this.w = w; this.h = h; } area() { return this.w * this.h; } } class Circle extends Shape { constructor(r) { super(); this.r = r; } area() { return Math.floor(Math.PI * this.r * this.r); } } [new Rect(3, 4), new Circle(5)] .forEach(shape => console.log(shape.area()));
요약

이 단원의 정리

  • extends 로 물려받고 필요한 것만 더합니다.
  • 자식에 constructor 를 두면 super() 를 먼저 부릅니다.
  • 같은 이름의 메서드를 두면 자식 것이 쓰입니다. 부모 것은 super. 로 부릅니다.
  • 부모가 같으면 한 배열에 담아 같은 이름으로 부를 수 있습니다.
  • 세 단계를 넘어가면 물려받는 대신 넘겨받는 편이 낫습니다.