Menu
Русский

Классы в JavaScript: конструктор, методы и new

Разбираемся, как на самом деле устроены классы в JavaScript: конструктор, методы, свойства экземпляра, геттеры и сеттеры — и что скрывается за ключевым словом class.

Класс — это чертёж для объектов

Класс описывает, как устроен объект определённого типа и что он умеет делать. Вы один раз описываете «чертёж», а потом штампуете по нему столько экземпляров, сколько нужно. У каждого экземпляра свои данные, но методы — общие.

Базовый синтаксис класса в JavaScript выглядит так:

index.js
Output
Click Run to see the output here.

class User { ... } задаёт шаблон, а new User(...) создаёт экземпляр. У каждого экземпляра своё name и свой email, но метод greet один на всех — он живёт на самом классе.

Два экземпляра, два набора данных, один набор методов. В этом вся суть.

Конструктор класса в JavaScript инициализирует экземпляр

constructor — это особый метод класса, который вызывается ровно один раз при создании объекта через new. Его задача — подготовить новый объект, как правило, записав переданные аргументы в this:

index.js
Output
Click Run to see the output here.

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 выполняет четыре действия по порядку:

  1. Создаёт новый пустой объект.
  2. Привязывает его к прототипу класса (где хранятся методы).
  3. Вызывает constructor, где this указывает на этот новый объект.
  4. Возвращает получившийся объект.

Без new ничего из этого не произойдёт. Класс требует его обязательно — и это большой шаг вперёд по сравнению с эпохой до class, когда забытый new мог незаметно испортить глобальный объект.

Методы — общие, поля класса — у каждого экземпляра свои

Методы, описанные в теле класса, живут на прототипе — одна копия на всех, общая для всех экземпляров. А поля экземпляра (всё, что мы присваиваем через this) создаются заново для каждого объекта — у каждого свои.

index.js
Output
Click Run to see the output here.

У каждого экземпляра своё count, поэтому увеличение счётчика в одном объекте не затрагивает другой. А вот a.increment и b.increment — это буквально одна и та же функция: она хранится в единственном экземпляре на прототипе и подтягивается при каждом вызове у конкретного объекта. Именно поэтому классы дёшево масштабируются: тысяча экземпляров не создаёт тысячу копий каждого метода.

Поля класса в JavaScript

Поля экземпляра можно объявлять прямо в теле класса, вне конструктора:

index.js
Output
Click Run to see the output here.

Объявления полей выполняются до тела конструктора — как если бы они были прописаны в самом начале constructor. Это удобно, когда начальное значение не зависит от аргументов конструктора: лучше уж так, чем забивать конструктор строчками вроде this.count = 0; this.step = 1;.

А вот если значение всё-таки зависит от аргументов, присваивание логичнее оставить внутри конструктора — там, где эти аргументы доступны.

Геттеры и сеттеры в JavaScript

Геттер или сеттер внешне похож на метод, но ведёт себя как обращение к свойству. Вы читаете или присваиваете значение без скобок, а под капотом срабатывает функция:

index.js
Output
Click Run to see the output here.

Обрати внимание на то, как всё это вызывается: t.fahrenheit (без скобок) — это обращение к геттеру, а t.fahrenheit = 100 срабатывает как сеттер. Снаружи fahrenheit выглядит как самое обычное свойство, но его значение вычисляется на лету из celsius.

Геттеры удобны для производных значений. Сеттеры — для валидации или нормализации при присваивании. Только не злоупотребляй ими: если геттер выполняет тяжёлую работу, читающий код человек будет неприятно удивлён тем, что «простое обращение к свойству» на самом деле что-то считает.

Краткая запись методов, вычисляемые имена и this

Методы класса в javascript записываются в сокращённой форме — без ключевого слова function и без : между именем и телом:

index.js
Output
Click Run to see the output here.

Возврат this из add включает цепочку вызовов — каждый вызов возвращает тот же экземпляр, так что следующий метод работает уже на нём.

Есть один подводный камень: методы класса не привязываются к экземпляру автоматически. Если «оторвать» метод и вызвать его отдельно, this потеряется:

index.js
Output
Click Run to see the output here.

Вызов g.hello() работает, потому что точка подставляет this. А вот вызов fn() сам по себе — уже нет. Если нужен «оторванный» метод с заранее привязанным контекстом (частый случай в обработчиках событий), привяжите его в конструкторе или используйте поле класса со стрелочной функцией: hello = () => \Привет, ${this.name}`;`.

Небольшой рабочий пример

Соберём всё вместе — поля класса, конструктор, методы и геттер:

index.js
Output
Click Run to see the output here.

Всё понятно с первого взгляда: класс чётко описывает, что такое BankAccount и что с ним можно делать.

Классы в JavaScript — это на самом деле функции

Важный момент, который стоит усвоить: class — это по большей части синтаксический сахар. Под капотом класс — это функция, а точнее функция-конструктор, а её методы живут на ClassName.prototype:

index.js
Output
Click Run to see the output here.

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, а не писать конструкторы руками.

Учитесь программировать с Coddy

НАЧАТЬ