Класс - это чертёж для объектов
Класс описывает, как устроен объект определённого типа и что он умеет делать. Вы один раз описываете «чертёж», а потом штампуете по нему столько экземпляров, сколько нужно. У каждого экземпляра свои данные, но методы - общие.
Базовый синтаксис класса в JavaScript выглядит так:
class User { ... } задаёт шаблон, а new User(...) создаёт экземпляр. У каждого экземпляра своё name и свой email, но метод greet один на всех - он живёт на самом классе.
Два экземпляра, два набора данных, один набор методов. В этом вся суть.
Конструктор класса в JavaScript инициализирует экземпляр
constructor - это особый метод класса, который вызывается ровно один раз при создании объекта через new. Его задача - подготовить новый объект, как правило, записав переданные аргументы в this:
this внутри конструктора - это та самая свежесозданная сущность, которую мы как раз собираем. Когда мы пишем this.x = x, мы кладём x именно на этот объект. Каждый вызов new Point(...) получает собственный this, а значит, и свои собственные x и y.
Если конструктор не описать самому, JavaScript подставит пустой за нас. Писать его вручную имеет смысл только тогда, когда при создании объекта реально нужно что-то настроить.
Ключевое слово new - сердце всей механики
Вызов класса без new выбрасывает ошибку:
const p = Point(3, 4);
// TypeError: Class constructor Point cannot be invoked without 'new'
Это сделано специально. Оператор new выполняет четыре действия по порядку:
- Создаёт новый пустой объект.
- Привязывает его к прототипу класса (где хранятся методы).
- Вызывает
constructor, гдеthisуказывает на этот новый объект. - Возвращает получившийся объект.
Без new ничего из этого не произойдёт. Класс требует его обязательно - и это большой шаг вперёд по сравнению с эпохой до class, когда забытый new мог незаметно испортить глобальный объект.
Методы - общие, поля класса - у каждого экземпляра свои
Методы, описанные в теле класса, живут на прототипе - одна копия на всех, общая для всех экземпляров. А поля экземпляра (всё, что мы присваиваем через this) создаются заново для каждого объекта - у каждого свои.
У каждого экземпляра своё count, поэтому увеличение счётчика в одном объекте не затрагивает другой. А вот a.increment и b.increment - это буквально одна и та же функция: она хранится в единственном экземпляре на прототипе и подтягивается при каждом вызове у конкретного объекта. Именно поэтому классы дёшево масштабируются: тысяча экземпляров не создаёт тысячу копий каждого метода.
Поля класса в JavaScript
Поля экземпляра можно объявлять прямо в теле класса, вне конструктора:
Объявления полей выполняются до тела конструктора - как если бы они были прописаны в самом начале constructor. Это удобно, когда начальное значение не зависит от аргументов конструктора: лучше уж так, чем забивать конструктор строчками вроде this.count = 0; this.step = 1;.
А вот если значение всё-таки зависит от аргументов, присваивание логичнее оставить внутри конструктора - там, где эти аргументы доступны.
Геттеры и сеттеры в JavaScript
Геттер или сеттер внешне похож на метод, но ведёт себя как обращение к свойству. Вы читаете или присваиваете значение без скобок, а под капотом срабатывает функция:
Обрати внимание на то, как всё это вызывается: t.fahrenheit (без скобок) - это обращение к геттеру, а t.fahrenheit = 100 срабатывает как сеттер. Снаружи fahrenheit выглядит как самое обычное свойство, но его значение вычисляется на лету из celsius.
Геттеры удобны для производных значений. Сеттеры - для валидации или нормализации при присваивании. Только не злоупотребляй ими: если геттер выполняет тяжёлую работу, читающий код человек будет неприятно удивлён тем, что «простое обращение к свойству» на самом деле что-то считает.
Краткая запись методов, вычисляемые имена и this
Методы класса в javascript записываются в сокращённой форме - без ключевого слова function и без : между именем и телом:
Возврат this из add включает цепочку вызовов - каждый вызов возвращает тот же экземпляр, так что следующий метод работает уже на нём.
Есть один подводный камень: методы класса не привязываются к экземпляру автоматически. Если «оторвать» метод и вызвать его отдельно, this потеряется:
Вызов g.hello() работает, потому что точка подставляет this. А вот вызов fn() сам по себе - уже нет. Если нужен «оторванный» метод с заранее привязанным контекстом (частый случай в обработчиках событий), привяжите его в конструкторе или используйте поле класса со стрелочной функцией: hello = () => \Привет, ${this.name}`;`.
Небольшой рабочий пример
Соберём всё вместе - поля класса, конструктор, методы и геттер:
Всё понятно с первого взгляда: класс чётко описывает, что такое BankAccount и что с ним можно делать.
Классы в JavaScript - это на самом деле функции
Важный момент, который стоит усвоить: class - это по большей части синтаксический сахар. Под капотом класс - это функция, а точнее функция-конструктор, а её методы живут на ClassName.prototype:
typeof User вернёт "function", а метод greet лежит на User.prototype. Экземпляры находят greet, проходя по цепочке прототипов - ровно по тому же механизму, который есть в языке с самого начала. Классы - это просто удобный синтаксис поверх него.
Такое понимание здорово выручает дальше: и наследование, и instanceof, и отладка цепочек прототипов становятся куда понятнее, если помнить - класс в JavaScript это функция, у которой на прототипе висят методы.
Что дальше: наследование
Пока что каждый класс живёт сам по себе. Но в реальных проектах классы почти всегда выстраиваются в иерархию: Dog - это разновидность Animal, AdminUser - разновидность User. За это отвечают ключевые слова extends и super - к ним и перейдём дальше.
Часто задаваемые вопросы
Как объявить класс в JavaScript?
Пишете ключевое слово class, дальше имя, а в теле - метод constructor и остальные методы. Экземпляр создаётся через new ИмяКласса(...). Например: class User { constructor(name) { this.name = name; } }, а затем new User('Ada').
Для чего нужен constructor в классе?
constructor срабатывает один раз - в момент вызова new ИмяКласса(...). Его задача - подготовить новый экземпляр: как правило, присвоить переданные аргументы в this как свойства объекта. Если вы его не опишете, JavaScript молча подставит пустой конструктор по умолчанию.
Чем класс отличается от функции в JavaScript?
Под капотом класс - это та же функция, а точнее функция-конструктор, у которой методы лежат в прототипе. class - во многом синтаксический сахар, но с важными нюансами: его нельзя вызвать без new, он даёт аккуратный extends, а методы по умолчанию становятся неперечисляемыми. В новом коде стоит использовать именно class, а не писать конструкторы руками.