Хотите заказать веб-сайт? Связаться с нами

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. Он выполняет следующие действия:

  1. создает новый пустой объект
  2. привязывает этот объект к this внутри конструктора
  3. выполняет код конструктора
  4. возвращает созданный объект

Если при создании объекта передать меньше аргументов, чем ожидает конструктор, недостающие параметры получат значение 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.

Приватные поля

Приватные поля обеспечивают настоящую инкапсуляцию данных в классе. До их появления разработчики использовали различные соглашения (например, нижнее подчеркивание), но это не давало реальной защиты. Современный JavaScript предлагает синтаксис с символом #, который делает поле недоступным извне.

Пример 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, принимает один параметр и не возвращает результат. Важно: геттеры и сеттеры вызываются без круглых скобок, как обычные свойства.

Типичные сценарии использования

  • валидация данных при установке значения
  • вычисляемые свойства на основе других полей
  • логирование операций доступа
  • преобразование форматов данных

Наследование классов

Наследование классов позволяет создавать иерархии, где дочерние классы получают функциональность родительских и могут расширять или изменять её. Это краеугольный камень объектно-ориентированного программирования, реализуемый в JavaScript через ключевое слово 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, на котором построены классы. Каждый объект имеет скрытую ссылку на свой прототип — другой объект, от которого он наследует свойства и методы.

При обращении к свойству объекта 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, существуют в единственном экземпляре для всех объектов класса. Это экономит память, особенно когда создается множество объектов с большим количеством методов.

Важно понимать разницу:

  1. собственные свойства — объявленные в конструкторе через this, уникальны для каждого экземпляра
  2. прототипные свойства — добавленные через 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

Статические свойства особенно полезны для хранения конфигураций, общих для всех экземпляров. Статические методы часто используются для альтернативных способов создания объектов.

Проверка принадлежности к классу

Проверка принадлежности к классу необходима, когда код должен по-разному обрабатывать объекты разных типов. JavaScript предоставляет несколько операторов для этих целей.

Пример 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 ClassName().

Что произойдёт, если не вызвать super() в дочернем классе?

JavaScript выдаст ошибку ссылки. Вызов super() обязателен в конструкторе наследника, поскольку именно он инициализирует привязку this. Попытка обратиться к this до вызова super() также приведёт к ошибке.

Приватные поля с # действительно приватны?

Да, это настоящая приватность на уровне языка. К полям с # невозможно обратиться извне, они не наследуются и не видны в дочерних классах. Никакие обходные пути вроде Object.getOwnPropertySymbols() здесь не работают.

Чем геттер отличается от обычного метода?

Геттер вызывается без круглых скобок и выглядит как свойство. Это позволяет позже заменить прямое поле на вычисляемое значение без изменения клиентского кода. Геттер не может принимать аргументы и должен что-то возвращать.

Когда использовать статические методы?

Статические методы хороши для утилит, фабрик и вспомогательных функций, логически связанных с классом, но не требующих создания экземпляра. Типичные примеры: Math.max(), валидаторы данных, счётчики созданных объектов.

Заключение

Классы в JavaScript — это мощный и выразительный инструмент, который делает код более организованным и понятным. Они не вводят принципиально новую модель наследования, но предоставляют удобный синтаксис для работы с существующей прототипной системой.

Освоив эту тему, вы сделали важный шаг, но это лишь начало пути. Хотите пройти его быстро и без пробелов? Записывайтесь на онлайн-курс «Обучение JavaScript с нуля до профи»: реальные проекты и поддержка. А если нужен мощный сайт для бизнеса — наша веб-студия создаст его под ключ. Напишите в Telegram или оставьте заявку — обсудим и приступим завтра.

Теги: