- Главная
- Javascript
- JavaScript классы: основы и примеры
JavaScript классы: основы и примеры
Без понимания классов в javascript в современной веб-разработке будет трудно. Этот мощный инструмент позволяет структурировать код, делать его более организованным и масштабируемым. В данной статье мы подробно разберем все аспекты работы с классами: от базового синтаксиса до продвинутых возможностей, таких как наследование и прототипы.
Что такое классы и зачем они нужны
До появления классов в ES6 разработчики использовали функции-конструкторы. Современный синтаксис классов не вводит новую объектно-ориентированную модель, а предоставляет более удобный и понятный способ работы с прототипным наследованием. Под капотом классы остаются функциями, но код становится чище и логичнее.
Основные преимущества использования классов
- структурированность — весь код, относящийся к одной сущности, находится в одном месте
- переиспользование — один класс может создавать множество объектов
- наследование — возможность расширять функциональность без дублирования кода
- инкапсуляция — контроль доступа к внутренним данным
Базовый синтаксис класса
class, за которым следует имя класса. По соглашению имена классов пишутся с заглавной буквы.Пример 1
class Employee {
constructor(name, position) {
this.name = name;
this.position = position;
}
}
const employee1 = new Employee('Анна', 'разработчик');
const employee2 = new Employee('Павел', 'дизайнер');
Конструктор (constructor) — это специальный метод, который автоматически вызывается при создании нового экземпляра класса. Внутри конструктора мы инициализируем свойства объекта с помощью ключевого слова this, которое ссылается на создаваемый экземпляр.
Для создания объекта используется оператор new. Он выполняет следующие действия:
- создает новый пустой объект
- привязывает этот объект к
thisвнутри конструктора - выполняет код конструктора
- возвращает созданный объект
Если при создании объекта передать меньше аргументов, чем ожидает конструктор, недостающие параметры получат значение undefined. Эту проблему решают параметры по умолчанию:
Пример 2
class Employee {
constructor(name, position = 'стажер') {
this.name = name;
this.position = position;
}
}
const trainee = new Employee('Елена');
console.log(trainee.position); // стажер
Методы класса
function и автоматически добавляются в прототип, что экономит память при создании множества экземпляров.Пример 3
class Calculator {
constructor(value = 0) {
this.currentValue = value;
}
add(number) {
this.currentValue += number;
return this;
}
subtract(number) {
this.currentValue -= number;
return this;
}
getResult() {
return this.currentValue;
}
}
const calc = new Calculator(10);
const result = calc.add(5).subtract(3).getResult();
console.log(result); // 12
В примере выше методы возвращают this, что позволяет выстраивать цепочки вызовов — удобный паттерн, часто используемый в реальных проектах.
Методы могут принимать параметры, возвращать значения, взаимодействовать с другими методами класса. Главное отличие методов от обычных функций: они всегда имеют доступ к текущему экземпляру через this.
Приватные поля
#, который делает поле недоступным извне.Пример 4
class BankAccount {
#balance;
#currency;
constructor(initialBalance, currency = 'RUB') {
this.#balance = initialBalance;
this.#currency = currency;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
return true;
}
return false;
}
getBalance() {
return `${this.#balance} ${this.#currency}`;
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500 RUB
console.log(account.#balance); // SyntaxError: Private field
Приватные поля объявляются вне конструктора, в теле класса. Попытка обратиться к ним извне приведет к синтаксической ошибке. Это жесткое ограничение работает на уровне языка, а не на уровне договоренностей.
Оживи свой сайт. Освой JavaScript!
Статичная верстка — это только скелет. Наш онлайн-курс "JavaScript с нуля до профи" даст твоим страницам мышцы и нервы. Научись создавать слайдеры, формы, интерактивные карты и получать данные с сервера.
От теории — к реальным скриптам в твоём портфолио.
Геттеры и сеттеры
Пример 5
class User {
#firstName;
#lastName;
constructor(firstName, lastName) {
this.#firstName = firstName;
this.#lastName = lastName;
}
get fullName() {
return `${this.#firstName} ${this.#lastName}`;
}
set fullName(value) {
const parts = value.split(' ');
if (parts.length >= 2) {
this.#firstName = parts[0];
this.#lastName = parts.slice(1).join(' ');
}
}
get firstName() {
return this.#firstName;
}
set firstName(value) {
if (value.length >= 2) {
this.#firstName = value;
}
}
}
const user = new User('Иван', 'Петров');
console.log(user.fullName); // Иван Петров
user.fullName = 'Мария Иванова';
console.log(user.firstName); // Мария
Геттер определяется с ключевым словом get, не принимает параметров и возвращает значение. Сеттер использует ключевое слово set, принимает один параметр и не возвращает результат. Важно: геттеры и сеттеры вызываются без круглых скобок, как обычные свойства.
Типичные сценарии использования
- валидация данных при установке значения
- вычисляемые свойства на основе других полей
- логирование операций доступа
- преобразование форматов данных
Наследование классов
extends.Пример 6
class Device {
constructor(brand) {
this.brand = brand;
this.power = false;
}
turnOn() {
this.power = true;
console.log(`${this.brand} включен`);
}
turnOff() {
this.power = false;
console.log(`${this.brand} выключен`);
}
}
class Smartphone extends Device {
constructor(brand, os) {
super(brand);
this.os = os;
this.apps = [];
}
installApp(appName) {
this.apps.push(appName);
console.log(`Установлено: ${appName}`);
}
turnOn() {
super.turnOn();
console.log(`Загрузка ${this.os}...`);
}
}
const iphone = new Smartphone('Apple', 'iOS');
iphone.turnOn();
iphone.installApp('Telegram');
Ключевые моменты наследования
- вызов
super()в конструкторе дочернего класса обязателен — он инициализирует родительский конструктор - через
superтакже можно вызывать методы родителя, даже переопределенные в дочернем классе - цепочка наследования может быть любой глубины
- класс может наследовать только от одного родителя (множественное наследование отсутствует)
Наследование позволяет реализовать принцип DRY (Don’t Repeat Yourself), вынося общую логику в базовые классы.
Прототипы
При обращении к свойству объекта JavaScript сначала ищет его в самом объекте, а затем поднимается по цепочке прототипов.
Свойство prototype существует только у функций-конструкторов и классов. Экземпляры имеют внутреннее свойство [[Prototype]], доступное через Object.getPrototypeOf() или устаревшее __proto__.
Пример 7
class Book {
constructor(title, author) {
this.title = title;
this.author = author;
}
getInfo() {
return `${this.title} — ${this.author}`;
}
}
// Добавление метода через prototype
Book.prototype.getYearPublished = function() {
return this.year || 'неизвестно';
};
const book = new Book('1984', 'Джордж Оруэлл');
book.year = 1949;
console.log(book.getYearPublished()); // 1949
// Проверка принадлежности
console.log(book.hasOwnProperty('title')); // true
console.log(book.hasOwnProperty('getYearPublished')); // false
Методы, добавленные через prototype, существуют в единственном экземпляре для всех объектов класса. Это экономит память, особенно когда создается множество объектов с большим количеством методов.
Важно понимать разницу:
- собственные свойства — объявленные в конструкторе через
this, уникальны для каждого экземпляра - прототипные свойства — добавленные через
ClassName.prototype, общие для всех экземпляров
Изменение прототипа встроенных объектов (Array, Object, String) считается плохой практикой, так как может привести к конфликтам и непредсказуемому поведению в чужих библиотеках.
Статические методы и свойства
static и вызываются через имя класса, а не через объекты.Типичное применение — фабричные методы, утилиты, счетчики экземпляров.
Пример 8
class FileProcessor {
static allowedExtensions = ['txt', 'json', 'csv'];
static instanceCount = 0;
constructor(filename) {
this.filename = filename;
FileProcessor.instanceCount++;
}
static isValidExtension(filename) {
const extension = filename.split('.').pop();
return FileProcessor.allowedExtensions.includes(extension);
}
static createFromPath(path) {
const filename = path.split('/').pop();
return new FileProcessor(filename);
}
}
const isValid = FileProcessor.isValidExtension('document.pdf');
console.log(isValid); // false
const file = FileProcessor.createFromPath('/home/user/data.json');
console.log(file.filename); // data.json
console.log(FileProcessor.instanceCount); // 1
Статические свойства особенно полезны для хранения конфигураций, общих для всех экземпляров. Статические методы часто используются для альтернативных способов создания объектов.
Проверка принадлежности к классу
Пример 9
class Animal {}
class Cat extends Animal {}
class Dog extends Animal {}
const barsik = new Cat();
const rex = new Dog();
console.log(barsik instanceof Cat); // true
console.log(barsik instanceof Animal); // true
console.log(barsik instanceof Dog); // false
console.log(rex instanceof Animal); // true
console.log(barsik.constructor.name); // Cat
console.log(typeof barsik); // object
Оператор instanceof проверяет наличие конструктора указанного класса (или родительских классов) в цепочке прототипов объекта. Свойство constructor.name возвращает имя класса-конструктора, но его надежность ограничена — имя может быть изменено, а код может быть минифицирован.
Практические рекомендации
При работе с классами важно придерживаться определенных принципов, которые сделают код более надежным и поддерживаемым.
Композиция вместо наследования. Глубокая иерархия классов усложняет поддержку. Часто лучше использовать композицию — включать объекты других классов как свойства.
Пример 10
// Предпочтительнее
class Engine {
start() { /* ... */ }
}
class Car {
constructor() {
this.engine = new Engine();
}
start() {
this.engine.start();
}
}
// Чем глубокое наследование
class Vehicle { /* ... */ }
class LandVehicle extends Vehicle { /* ... */ }
class Car extends LandVehicle { /* ... */ }
Минимизируйте публичный интерфейс. Чем меньше методов и свойств доступно извне, тем проще изменять внутреннюю реализацию без риска сломать зависимый код.
Используйте приватные поля. Символ # обеспечивает настоящую защиту, а не просто соглашение. Это особенно важно в больших командах и библиотеках.
Избегайте модификации прототипов встроенных объектов. Это может привести к конфликтам при обновлении JavaScript или подключении сторонних библиотек.
Часто задаваемые вопросы
Чем классы отличаются от функций-конструкторов?
Классы — это «синтаксический сахар» над функциями-конструкторами, но с важными отличиями: код внутри класса всегда выполняется в строгом режиме, методы класса не перечислимы и не могут быть вызваны без new, а объявление класса не подвержено всплытию (hoisting).
Можно ли использовать классы без оператора new?
new?Нет, вызов класса без new приведёт к ошибке типа. Это принципиальное отличие от функций-конструкторов, которые могут быть вызваны и без new (хоть и с непредсказуемым поведением). Классы жёстко требуют конструкцию new ClassName().
Что произойдёт, если не вызвать super() в дочернем классе?
super() в дочернем классе?JavaScript выдаст ошибку ссылки. Вызов super() обязателен в конструкторе наследника, поскольку именно он инициализирует привязку this. Попытка обратиться к this до вызова super() также приведёт к ошибке.
Приватные поля с # действительно приватны?
# действительно приватны?Да, это настоящая приватность на уровне языка. К полям с # невозможно обратиться извне, они не наследуются и не видны в дочерних классах. Никакие обходные пути вроде Object.getOwnPropertySymbols() здесь не работают.
Чем геттер отличается от обычного метода?
Геттер вызывается без круглых скобок и выглядит как свойство. Это позволяет позже заменить прямое поле на вычисляемое значение без изменения клиентского кода. Геттер не может принимать аргументы и должен что-то возвращать.
Когда использовать статические методы?
Статические методы хороши для утилит, фабрик и вспомогательных функций, логически связанных с классом, но не требующих создания экземпляра. Типичные примеры: Math.max(), валидаторы данных, счётчики созданных объектов.
Заключение
Классы в JavaScript — это мощный и выразительный инструмент, который делает код более организованным и понятным. Они не вводят принципиально новую модель наследования, но предоставляют удобный синтаксис для работы с существующей прототипной системой.
Освоив эту тему, вы сделали важный шаг, но это лишь начало пути. Хотите пройти его быстро и без пробелов? Записывайтесь на онлайн-курс «Обучение JavaScript с нуля до профи»: реальные проекты и поддержка. А если нужен мощный сайт для бизнеса — наша веб-студия создаст его под ключ. Напишите в Telegram или оставьте заявку — обсудим и приступим завтра.