Класс — это чертёж для объектов
Класс описывает, как устроен объект определённого типа и что он умеет делать. Вы один раз описываете «чертёж», а потом штампуете по нему столько экземпляров, сколько нужно. У каждого экземпляра свои данные, но методы — общие.
Базовый синтаксис класса в 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, а не писать конструкторы руками.