TypeScript добавляет к классам JavaScript три модификатора доступа:
class Person {
public name: string // доступен везде (по умолчанию)
private age: number // только внутри класса
protected email: string // внутри класса и в подклассах
constructor(name: string, age: number, email: string) {
this.name = name
this.age = age
this.email = email
}
getInfo(): string {
return `${this.name}, возраст ${this.age}` // private доступен внутри
}
}
const p = new Person('Алексей', 30, 'alex@mail.ru')
console.log(p.name) // OK — public
// p.age // Ошибка TS: Property 'age' is private
// p.email // Ошибка TS: Property 'email' is protectedTypeScript позволяет объявить и инициализировать поля прямо в сигнатуре конструктора:
// Длинная запись:
class User {
private name: string
private age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
// Короткая запись — то же самое:
class User {
constructor(
private name: string,
private age: number
) {}
}Модификатор в параметре конструктора = объявление поля + присваивание в одну строку.
Поле можно задать только при создании, дальше — только чтение:
class Config {
readonly apiUrl: string
readonly version: string = '1.0.0' // можно с дефолтом
constructor(apiUrl: string) {
this.apiUrl = apiUrl // OK — в конструкторе можно
}
update() {
// this.apiUrl = '...' // Ошибка TS: Cannot assign to 'apiUrl' because it is a read-only property
}
}class Temperature {
private _celsius: number
constructor(celsius: number) {
this._celsius = celsius
}
get fahrenheit(): number {
return this._celsius * 9/5 + 32
}
set fahrenheit(value: number) {
this._celsius = (value - 32) * 5/9
}
get celsius(): number { return this._celsius }
set celsius(value: number) {
if (value < -273.15) throw new RangeError('Ниже абсолютного нуля')
this._celsius = value
}
}
const t = new Temperature(100)
console.log(t.fahrenheit) // 212
t.fahrenheit = 32
console.log(t.celsius) // 0Abstract класс — шаблон, который **нельзя инстанциировать напрямую**. Он определяет контракт для подклассов:
abstract class Shape {
abstract area(): number // абстрактный метод — без реализации
abstract perimeter(): number
// Обычный метод — с реализацией, наследуется
toString(): string {
return `Shape: area=${this.area().toFixed(2)}`
}
}
class Circle extends Shape {
constructor(private radius: number) { super() }
area(): number { return Math.PI * this.radius ** 2 }
perimeter(): number { return 2 * Math.PI * this.radius }
}
class Rectangle extends Shape {
constructor(private w: number, private h: number) { super() }
area(): number { return this.w * this.h }
perimeter(): number { return 2 * (this.w + this.h) }
}
// const s = new Shape() // Ошибка TS: Cannot create an instance of an abstract class
const c = new Circle(5)
console.log(c.toString()) // 'Shape: area=78.54'interface Printable {
print(): void
}
interface Serializable {
serialize(): string
}
// Класс может реализовать несколько интерфейсов
class Document implements Printable, Serializable {
constructor(private content: string) {}
print(): void {
console.log(this.content)
}
serialize(): string {
return JSON.stringify({ content: this.content })
}
}| Характеристика | abstract class | interface |
|---|---|---|
| Реализация методов | Может иметь | Нет (только типы) |
| Поля с состоянием | Да | Нет |
| Конструктор | Да | Нет |
| Наследование | extends (один) | implements (несколько) |
| Компилируется в JS | Да (класс) | Нет (только типы) |
**Правило**: используй interface для описания формы данных/контракта API, abstract class — когда нужна общая логика с обязательными точками расширения.
Иерархия классов: абстрактная фигура Shape, Rectangle, Circle, Triangle с вычислением площадей
// В TypeScript это было бы abstract class Shape
// В JavaScript эмулируем через обычный класс с проверкой в runtime
class Shape {
constructor(color = 'black') {
if (new.target === Shape) {
throw new Error('Shape — абстрактный класс, нельзя создать напрямую')
}
this.color = color
}
// "Абстрактный" метод — подклассы обязаны переопределить
area() {
throw new Error(`${this.constructor.name} должен реализовать area()`)
}
perimeter() {
throw new Error(`${this.constructor.name} должен реализовать perimeter()`)
}
// Конкретный метод — наследуется всеми подклассами
toString() {
return (
`[${this.constructor.name}] ` +
`площадь=${this.area().toFixed(2)}, ` +
`периметр=${this.perimeter().toFixed(2)}, ` +
`цвет=${this.color}`
)
}
// TypeScript: implements Comparable
isLargerThan(other) {
return this.area() > other.area()
}
}
class Rectangle extends Shape {
// TypeScript: constructor(private w: number, private h: number)
constructor(w, h, color) {
super(color)
this.w = w
this.h = h
}
area() { return this.w * this.h }
perimeter() { return 2 * (this.w + this.h) }
}
class Circle extends Shape {
constructor(radius, color) {
super(color)
this.radius = radius
}
area() { return Math.PI * this.radius ** 2 }
perimeter() { return 2 * Math.PI * this.radius }
// Getter — как в TypeScript get diameter()
get diameter() { return this.radius * 2 }
}
class Triangle extends Shape {
constructor(a, b, c, color) {
super(color)
if (a + b <= c || a + c <= b || b + c <= a) {
throw new Error('Невалидный треугольник')
}
this.a = a
this.b = b
this.c = c
}
perimeter() { return this.a + this.b + this.c }
area() {
// Формула Герона
const s = this.perimeter() / 2
return Math.sqrt(s * (s - this.a) * (s - this.b) * (s - this.c))
}
}
// --- Демонстрация ---
const shapes = [
new Rectangle(10, 5, 'red'),
new Circle(7, 'blue'),
new Triangle(3, 4, 5, 'green'),
new Rectangle(6, 6, 'yellow'),
]
console.log('=== Все фигуры ===')
shapes.forEach(s => console.log(s.toString()))
console.log('\n=== Сортировка по площади (возр.) ===')
const sorted = [...shapes].sort((a, b) => a.area() - b.area())
sorted.forEach(s => console.log(`${s.constructor.name}: ${s.area().toFixed(2)}`))
console.log('\n=== Итого ===')
const totalArea = shapes.reduce((sum, s) => sum + s.area(), 0)
console.log(`Суммарная площадь: ${totalArea.toFixed(2)}`)
const circle = shapes[1]
console.log(`\nДиаметр круга: ${circle.diameter}`)
console.log(`Прямоугольник больше круга? ${shapes[0].isLargerThan(circle)}`)
// Попытка создать Shape напрямую
try {
new Shape('purple')
} catch (e) {
console.log(`\nОшибка: ${e.message}`)
}TypeScript добавляет к классам JavaScript три модификатора доступа:
class Person {
public name: string // доступен везде (по умолчанию)
private age: number // только внутри класса
protected email: string // внутри класса и в подклассах
constructor(name: string, age: number, email: string) {
this.name = name
this.age = age
this.email = email
}
getInfo(): string {
return `${this.name}, возраст ${this.age}` // private доступен внутри
}
}
const p = new Person('Алексей', 30, 'alex@mail.ru')
console.log(p.name) // OK — public
// p.age // Ошибка TS: Property 'age' is private
// p.email // Ошибка TS: Property 'email' is protectedTypeScript позволяет объявить и инициализировать поля прямо в сигнатуре конструктора:
// Длинная запись:
class User {
private name: string
private age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
// Короткая запись — то же самое:
class User {
constructor(
private name: string,
private age: number
) {}
}Модификатор в параметре конструктора = объявление поля + присваивание в одну строку.
Поле можно задать только при создании, дальше — только чтение:
class Config {
readonly apiUrl: string
readonly version: string = '1.0.0' // можно с дефолтом
constructor(apiUrl: string) {
this.apiUrl = apiUrl // OK — в конструкторе можно
}
update() {
// this.apiUrl = '...' // Ошибка TS: Cannot assign to 'apiUrl' because it is a read-only property
}
}class Temperature {
private _celsius: number
constructor(celsius: number) {
this._celsius = celsius
}
get fahrenheit(): number {
return this._celsius * 9/5 + 32
}
set fahrenheit(value: number) {
this._celsius = (value - 32) * 5/9
}
get celsius(): number { return this._celsius }
set celsius(value: number) {
if (value < -273.15) throw new RangeError('Ниже абсолютного нуля')
this._celsius = value
}
}
const t = new Temperature(100)
console.log(t.fahrenheit) // 212
t.fahrenheit = 32
console.log(t.celsius) // 0Abstract класс — шаблон, который **нельзя инстанциировать напрямую**. Он определяет контракт для подклассов:
abstract class Shape {
abstract area(): number // абстрактный метод — без реализации
abstract perimeter(): number
// Обычный метод — с реализацией, наследуется
toString(): string {
return `Shape: area=${this.area().toFixed(2)}`
}
}
class Circle extends Shape {
constructor(private radius: number) { super() }
area(): number { return Math.PI * this.radius ** 2 }
perimeter(): number { return 2 * Math.PI * this.radius }
}
class Rectangle extends Shape {
constructor(private w: number, private h: number) { super() }
area(): number { return this.w * this.h }
perimeter(): number { return 2 * (this.w + this.h) }
}
// const s = new Shape() // Ошибка TS: Cannot create an instance of an abstract class
const c = new Circle(5)
console.log(c.toString()) // 'Shape: area=78.54'interface Printable {
print(): void
}
interface Serializable {
serialize(): string
}
// Класс может реализовать несколько интерфейсов
class Document implements Printable, Serializable {
constructor(private content: string) {}
print(): void {
console.log(this.content)
}
serialize(): string {
return JSON.stringify({ content: this.content })
}
}| Характеристика | abstract class | interface |
|---|---|---|
| Реализация методов | Может иметь | Нет (только типы) |
| Поля с состоянием | Да | Нет |
| Конструктор | Да | Нет |
| Наследование | extends (один) | implements (несколько) |
| Компилируется в JS | Да (класс) | Нет (только типы) |
**Правило**: используй interface для описания формы данных/контракта API, abstract class — когда нужна общая логика с обязательными точками расширения.
Иерархия классов: абстрактная фигура Shape, Rectangle, Circle, Triangle с вычислением площадей
// В TypeScript это было бы abstract class Shape
// В JavaScript эмулируем через обычный класс с проверкой в runtime
class Shape {
constructor(color = 'black') {
if (new.target === Shape) {
throw new Error('Shape — абстрактный класс, нельзя создать напрямую')
}
this.color = color
}
// "Абстрактный" метод — подклассы обязаны переопределить
area() {
throw new Error(`${this.constructor.name} должен реализовать area()`)
}
perimeter() {
throw new Error(`${this.constructor.name} должен реализовать perimeter()`)
}
// Конкретный метод — наследуется всеми подклассами
toString() {
return (
`[${this.constructor.name}] ` +
`площадь=${this.area().toFixed(2)}, ` +
`периметр=${this.perimeter().toFixed(2)}, ` +
`цвет=${this.color}`
)
}
// TypeScript: implements Comparable
isLargerThan(other) {
return this.area() > other.area()
}
}
class Rectangle extends Shape {
// TypeScript: constructor(private w: number, private h: number)
constructor(w, h, color) {
super(color)
this.w = w
this.h = h
}
area() { return this.w * this.h }
perimeter() { return 2 * (this.w + this.h) }
}
class Circle extends Shape {
constructor(radius, color) {
super(color)
this.radius = radius
}
area() { return Math.PI * this.radius ** 2 }
perimeter() { return 2 * Math.PI * this.radius }
// Getter — как в TypeScript get diameter()
get diameter() { return this.radius * 2 }
}
class Triangle extends Shape {
constructor(a, b, c, color) {
super(color)
if (a + b <= c || a + c <= b || b + c <= a) {
throw new Error('Невалидный треугольник')
}
this.a = a
this.b = b
this.c = c
}
perimeter() { return this.a + this.b + this.c }
area() {
// Формула Герона
const s = this.perimeter() / 2
return Math.sqrt(s * (s - this.a) * (s - this.b) * (s - this.c))
}
}
// --- Демонстрация ---
const shapes = [
new Rectangle(10, 5, 'red'),
new Circle(7, 'blue'),
new Triangle(3, 4, 5, 'green'),
new Rectangle(6, 6, 'yellow'),
]
console.log('=== Все фигуры ===')
shapes.forEach(s => console.log(s.toString()))
console.log('\n=== Сортировка по площади (возр.) ===')
const sorted = [...shapes].sort((a, b) => a.area() - b.area())
sorted.forEach(s => console.log(`${s.constructor.name}: ${s.area().toFixed(2)}`))
console.log('\n=== Итого ===')
const totalArea = shapes.reduce((sum, s) => sum + s.area(), 0)
console.log(`Суммарная площадь: ${totalArea.toFixed(2)}`)
const circle = shapes[1]
console.log(`\nДиаметр круга: ${circle.diameter}`)
console.log(`Прямоугольник больше круга? ${shapes[0].isLargerThan(circle)}`)
// Попытка создать Shape напрямую
try {
new Shape('purple')
} catch (e) {
console.log(`\nОшибка: ${e.message}`)
}Реализуй класс `BankAccount` с приватными полями `#balance` и `#owner`. Методы: `deposit(amount)` — пополнение (бросает Error если amount <= 0), `withdraw(amount)` — снятие (бросает Error "Недостаточно средств" если не хватает, бросает Error если amount <= 0), геттер `getBalance()` — возвращает текущий баланс, геттер `owner` — имя владельца.
Используй приватные поля #balance и #owner (синтаксис ES2022). В withdraw проверь сначала что amount > 0, затем что this.#balance >= amount. Геттер объявляется через get getBalance() { return this.#balance }.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке