Класс

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

function Company(name, email) {
  this.name = name;
  this.email = email;
};

Company.prototype.getName = function getName() {
  return this.name;
}

Company.prototype.getEmail = function getEmail() {
  return this.email;
}

Company.prototype.setEmail = function setEmail(email) {
  this.email = email;
}

const company = new Company('Hexlet');
console.log(company.getName()); // => "Hexlet"

Этот код можно представить классом:

// Каждый класс должен лежать в своем собственном файле
// Идеально если имя класса совпадает с именем файла с учетом регистра
class Company { // имя класса это имя функции конструктора
  // Метод с именем constructor соответствует функции-конструктору
  // Он вызывается когда мы делаем new Company(name, email)
  construсtor(name, email) {
    this.name = name;
    this.email = email;
  }

  // Это свойство getName с записанной в него обычной (function) функцией
  getName() {
    return this.name;
  }

  getEmail() {
    return this.email;
  }

  setEmail(email) {
    this.email = email;
  }
}

// С точки зрения использования не меняется ничего
const company = new Company('Hexlet', '[email protected]'); // вызывается метод constructor
console.log(company.getName()); // => "Hexlet"

Метод соответствующий функции-конструктору внутри класса называется constructor. Интерпретатор вызывает его автоматически при создании нового объекта через new. Если конструктор отсутствует – то ничего страшного не произойдет. Это равносильно созданию и вызова пустой функции-конструктора:

function Company() {

}

const company = new Company();

Классы повышают уровень абстракции и дают разработчикам инструмент, к которому они привыкли по другим языкам. Они, действительно, позволяют не задумываться о прототипах, лежащих в их основе. По крайней мере, пока речь идет о написании прикладного кода. А вот во время отладки, без знания прототипов не обойтись. Кроме того, они иногда напрямую используются в библиотеках, потому что позволяют сделать вещи, которые нельзя сделать с помощью классов.

Свойства

В объектах встречаются свойства имеющие значения по умолчанию. Например у компании могут быть сотрудники. Если в компанию не был добавлен ни один сотрудник, то метод возвращающий их список должен вернуть пустой массив (если он этого не сделает, то это сильно усложнит разработку, так как везде придется делать проверки на существование):

const company = new Company();
company.getEmployees(); // []

Как этого добиться? Без классов это делается прямо внутри функции-конструктора:

function Company() {
  this.employees = [];
}

Так же поступают и в классах:

class Company {
  construtor() {
    this.employees = [];
  }
  // остальные методы
}

Однако есть другой способ. Он популярен в других языках, но в JavaScript его только начинают использовать. Этот способ основан на новом синтакисе (находится в процессе включения в стандарт и пока работает только через специальный плагин Babel) определения свойств внутри класса:

class Company {
  employees = [];
}

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

Подводные камни

Классы в JavaScript не статичны, как это сделано во многих языках, где один раз определив класс, больше нет возможности его менять. Мы по прежнему работаем с прототипами и функциями выполняющими роль конструкторов. Мы можем написать такой код после того как класс был определен:

// В любом месте программы после определения класса
Company.prototype.greeting = function greeting() {
  return `Hello, ${this.name}!`;
}

const company = new Company('Hexlet');
console.log(company.greeting()); // => "Hello, Hexlet!"

Как правильно использовать классы?

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

ООП в принципе и классы в частности – сложные концепции, которые невозможно выучить заранее по курсам, видео и статьям. Единственный способ разобраться – писать продакшен код, совершать ошибки и исправлять их (за счет советов более опытных разработчиков).

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


Дополнительные материалы

  1. @babel/plugin-proposal-class-properties
Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →