Представь, что ты разрабатываешь интернет-магазин. У тебя есть товары разных типов: книги, электроника, одежда. У всех есть общие поля — название, цена, количество. Но у каждого типа есть и свои: у книг — автор, у электроники — гарантия, у одежды — размер.
Без наследования придётся дублировать одни и те же методы (добавить в корзину, показать цену) в каждом классе. Наследование позволяет описать общее один раз, а в подклассах добавить только специфическое.
class Product {
constructor(name, price) {
this.name = name
this.price = price
}
getInfo() {
return `${this.name} — ${this.price} ₽`
}
}
class Book extends Product {
constructor(name, price, author) {
super(name, price) // ОБЯЗАТЕЛЬНО первым! Иначе ReferenceError
this.author = author
}
getInfo() {
return super.getInfo() + ` (автор: ${this.author})`
}
}
const book = new Book('Чистый код', 1200, 'Роберт Мартин')
console.log(book.getInfo())
// 'Чистый код — 1200 ₽ (автор: Роберт Мартин)'Правило: в конструкторе дочернего класса super() должен вызываться до любого обращения к this. Нарушение — ReferenceError.
Дочерний класс может полностью заменить метод родителя или дополнить его:
class Electronics extends Product {
constructor(name, price, warranty) {
super(name, price)
this.warranty = warranty // гарантия в месяцах
}
// Полное переопределение
getInfo() {
return `${this.name} — ${this.price} ₽, гарантия ${this.warranty} мес.`
}
// Новый метод только для Electronics
isUnderWarranty(monthsOld) {
return monthsOld < this.warranty
}
}Разные объекты реагируют по-своему на один и тот же вызов метода. Это позволяет писать универсальный код:
const cart = [
new Book('JavaScript', 990, 'Кайл Симпсон'),
new Electronics('MacBook Pro', 180000, 24),
new Product('Карандаш', 50),
]
// Один и тот же метод — разный результат у каждого объекта
cart.forEach(item => console.log(item.getInfo()))
// 'JavaScript — 990 ₽ (автор: Кайл Симпсон)'
// 'MacBook Pro — 180000 ₽, гарантия 24 мес.'
// 'Карандаш — 50 ₽'const laptop = new Electronics('HP Laptop', 60000, 12)
console.log(laptop instanceof Electronics) // true
console.log(laptop instanceof Product) // true — Electronics наследует Product
console.log(laptop instanceof Book) // false1. Забыли вызвать super() в конструкторе:
// Сломано:
class Book extends Product {
constructor(name, price, author) {
this.author = author // ReferenceError: Must call super before accessing 'this'
super(name, price)
}
}
// Исправлено:
class Book extends Product {
constructor(name, price, author) {
super(name, price) // сначала super
this.author = author
}
}2. Не вернули расширение из super.method():
// Сломано — потеряли строку родителя:
class Book extends Product {
getInfo() {
super.getInfo() // результат проигнорирован!
return `автор: ${this.author}`
}
}
// Исправлено:
class Book extends Product {
getInfo() {
return super.getInfo() + ` (автор: ${this.author})`
}
}3. Слишком глубокая иерархия:
// Запах кода — 4+ уровня наследования трудно отлаживать
class A extends B {}
class C extends A {}
class D extends C {} // уже сложно понять откуда что пришлоЕсли иерархия глубже 2-3 уровней — рассмотри композицию вместо наследования.
class MyComponent extends React.Component — все классовые компоненты наследуют базовый класс Reactclass CustomStream extends Readable — расширение встроенных потоковclass NotFoundError extends ErrorИерархия товаров интернет-магазина: Product, Book, Electronics
class Product {
constructor(name, price, stock = 0) {
this.name = name
this.price = price
this.stock = stock
}
getInfo() {
return `${this.name} — ${this.price.toLocaleString('ru-RU')} ₽`
}
isAvailable() {
return this.stock > 0
}
toString() {
return `[${this.constructor.name}: ${this.name}]`
}
}
class Book extends Product {
constructor(name, price, author, pages, stock) {
super(name, price, stock)
this.author = author
this.pages = pages
}
getInfo() {
return super.getInfo() + ` | ${this.author}, ${this.pages} стр.`
}
}
class Electronics extends Product {
constructor(name, price, brand, warranty, stock) {
super(name, price, stock)
this.brand = brand
this.warranty = warranty
}
getInfo() {
return super.getInfo() + ` | ${this.brand}, гарантия ${this.warranty} мес.`
}
isUnderWarranty(monthsOld) {
return monthsOld <= this.warranty
}
}
// Полиморфизм: один код для разных типов
const catalog = [
new Book('Чистый код', 1200, 'Роберт Мартин', 431, 5),
new Book('JavaScript: Хорошие части', 890, 'Дуглас Крокфорд', 176, 0),
new Electronics('iPhone 15', 89990, 'Apple', 12, 10),
new Electronics('AirPods Pro', 24990, 'Apple', 6, 3),
]
console.log('=== Каталог товаров ===')
catalog.forEach(item => {
const status = item.isAvailable() ? 'в наличии' : 'нет в наличии'
console.log(item.getInfo() + ` [${status}]`)
})
// 'Чистый код — 1 200 ₽ | Роберт Мартин, 431 стр. [в наличии]'
// 'JavaScript: Хорошие части — 890 ₽ | Дуглас Крокфорд, 176 стр. [нет в наличии]'
// 'iPhone 15 — 89 990 ₽ | Apple, гарантия 12 мес. [в наличии]'
// 'AirPods Pro — 24 990 ₽ | Apple, гарантия 6 мес. [в наличии]'
// instanceof — проверяем типы
const phone = catalog[2]
console.log(phone instanceof Electronics) // true
console.log(phone instanceof Product) // true
console.log(phone instanceof Book) // false
// Метод только у Electronics
if (phone instanceof Electronics) {
console.log('Гарантия действует 3 месяца?', phone.isUnderWarranty(3)) // true
}Представь, что ты разрабатываешь интернет-магазин. У тебя есть товары разных типов: книги, электроника, одежда. У всех есть общие поля — название, цена, количество. Но у каждого типа есть и свои: у книг — автор, у электроники — гарантия, у одежды — размер.
Без наследования придётся дублировать одни и те же методы (добавить в корзину, показать цену) в каждом классе. Наследование позволяет описать общее один раз, а в подклассах добавить только специфическое.
class Product {
constructor(name, price) {
this.name = name
this.price = price
}
getInfo() {
return `${this.name} — ${this.price} ₽`
}
}
class Book extends Product {
constructor(name, price, author) {
super(name, price) // ОБЯЗАТЕЛЬНО первым! Иначе ReferenceError
this.author = author
}
getInfo() {
return super.getInfo() + ` (автор: ${this.author})`
}
}
const book = new Book('Чистый код', 1200, 'Роберт Мартин')
console.log(book.getInfo())
// 'Чистый код — 1200 ₽ (автор: Роберт Мартин)'Правило: в конструкторе дочернего класса super() должен вызываться до любого обращения к this. Нарушение — ReferenceError.
Дочерний класс может полностью заменить метод родителя или дополнить его:
class Electronics extends Product {
constructor(name, price, warranty) {
super(name, price)
this.warranty = warranty // гарантия в месяцах
}
// Полное переопределение
getInfo() {
return `${this.name} — ${this.price} ₽, гарантия ${this.warranty} мес.`
}
// Новый метод только для Electronics
isUnderWarranty(monthsOld) {
return monthsOld < this.warranty
}
}Разные объекты реагируют по-своему на один и тот же вызов метода. Это позволяет писать универсальный код:
const cart = [
new Book('JavaScript', 990, 'Кайл Симпсон'),
new Electronics('MacBook Pro', 180000, 24),
new Product('Карандаш', 50),
]
// Один и тот же метод — разный результат у каждого объекта
cart.forEach(item => console.log(item.getInfo()))
// 'JavaScript — 990 ₽ (автор: Кайл Симпсон)'
// 'MacBook Pro — 180000 ₽, гарантия 24 мес.'
// 'Карандаш — 50 ₽'const laptop = new Electronics('HP Laptop', 60000, 12)
console.log(laptop instanceof Electronics) // true
console.log(laptop instanceof Product) // true — Electronics наследует Product
console.log(laptop instanceof Book) // false1. Забыли вызвать super() в конструкторе:
// Сломано:
class Book extends Product {
constructor(name, price, author) {
this.author = author // ReferenceError: Must call super before accessing 'this'
super(name, price)
}
}
// Исправлено:
class Book extends Product {
constructor(name, price, author) {
super(name, price) // сначала super
this.author = author
}
}2. Не вернули расширение из super.method():
// Сломано — потеряли строку родителя:
class Book extends Product {
getInfo() {
super.getInfo() // результат проигнорирован!
return `автор: ${this.author}`
}
}
// Исправлено:
class Book extends Product {
getInfo() {
return super.getInfo() + ` (автор: ${this.author})`
}
}3. Слишком глубокая иерархия:
// Запах кода — 4+ уровня наследования трудно отлаживать
class A extends B {}
class C extends A {}
class D extends C {} // уже сложно понять откуда что пришлоЕсли иерархия глубже 2-3 уровней — рассмотри композицию вместо наследования.
class MyComponent extends React.Component — все классовые компоненты наследуют базовый класс Reactclass CustomStream extends Readable — расширение встроенных потоковclass NotFoundError extends ErrorИерархия товаров интернет-магазина: Product, Book, Electronics
class Product {
constructor(name, price, stock = 0) {
this.name = name
this.price = price
this.stock = stock
}
getInfo() {
return `${this.name} — ${this.price.toLocaleString('ru-RU')} ₽`
}
isAvailable() {
return this.stock > 0
}
toString() {
return `[${this.constructor.name}: ${this.name}]`
}
}
class Book extends Product {
constructor(name, price, author, pages, stock) {
super(name, price, stock)
this.author = author
this.pages = pages
}
getInfo() {
return super.getInfo() + ` | ${this.author}, ${this.pages} стр.`
}
}
class Electronics extends Product {
constructor(name, price, brand, warranty, stock) {
super(name, price, stock)
this.brand = brand
this.warranty = warranty
}
getInfo() {
return super.getInfo() + ` | ${this.brand}, гарантия ${this.warranty} мес.`
}
isUnderWarranty(monthsOld) {
return monthsOld <= this.warranty
}
}
// Полиморфизм: один код для разных типов
const catalog = [
new Book('Чистый код', 1200, 'Роберт Мартин', 431, 5),
new Book('JavaScript: Хорошие части', 890, 'Дуглас Крокфорд', 176, 0),
new Electronics('iPhone 15', 89990, 'Apple', 12, 10),
new Electronics('AirPods Pro', 24990, 'Apple', 6, 3),
]
console.log('=== Каталог товаров ===')
catalog.forEach(item => {
const status = item.isAvailable() ? 'в наличии' : 'нет в наличии'
console.log(item.getInfo() + ` [${status}]`)
})
// 'Чистый код — 1 200 ₽ | Роберт Мартин, 431 стр. [в наличии]'
// 'JavaScript: Хорошие части — 890 ₽ | Дуглас Крокфорд, 176 стр. [нет в наличии]'
// 'iPhone 15 — 89 990 ₽ | Apple, гарантия 12 мес. [в наличии]'
// 'AirPods Pro — 24 990 ₽ | Apple, гарантия 6 мес. [в наличии]'
// instanceof — проверяем типы
const phone = catalog[2]
console.log(phone instanceof Electronics) // true
console.log(phone instanceof Product) // true
console.log(phone instanceof Book) // false
// Метод только у Electronics
if (phone instanceof Electronics) {
console.log('Гарантия действует 3 месяца?', phone.isUnderWarranty(3)) // true
}Ты разрабатываешь систему управления транспортом для сервиса аренды. Создай класс `Vehicle(make, model, year)` с методом `getInfo()`, возвращающим строку вида `"2020 Toyota Camry"`. Создай `Car extends Vehicle` с полем `doors` и override `getInfo()` — добавляет `", 4 дв."` к базовой строке. Создай `Truck extends Vehicle` с полем `payload` (грузоподъёмность в тоннах) и override `getInfo()` — добавляет `", грузоподъёмность: Xт"`. Все три класса должны правильно работать с `instanceof Vehicle`.
В getInfo() дочерних классов вызови super.getInfo() чтобы получить базовую строку, затем добавь к ней специфику: return super.getInfo() + ", 4 дв."