javascript

Синтаксический сахар или технический деготь: классы в JavaScript

  • среда, 21 января 2026 г. в 00:00:05
https://habr.com/ru/companies/selectel/articles/976550/

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

Сегодня мы сделаем следующий шаг к современному JavaScript. Я покажу вам классы — более чистый и понятный способ делать ровно то же самое. Хоть классы и называют «синтаксическим сахаром», но это не отменяет популярность и удобство их использования. Поехали!

От конструктора к классу

Прежде чем пойдем дальше, советую глянуть прошлую статью о функциях-конструкторах и операторе new. Если читать последовательно, то будет гораздо проще понять о чем я говорю здесь.

Давайте посмотрим на пример функции-конструктора из прошлой статьи, которая создает объекты товаров. Кстати, такие созданные объекты правильно называть экземплярами. Каждый new Product(...) порождает новый экземпляр (конкретный экземпляр товара) по общему шаблону.

function Product(name, price, discountPercent) {
 this.name = name;
 this.price = price;
 this.discountPercent = discountPercent;
 this.getFinalPrice = function () {
   const discountAmount = this.price * (this.discountPercent / 100);
   return this.price - discountAmount;
 };

 this.logInfo = function () {
   const finalPrice = this.getFinalPrice();
   console.log(${this.name});
   console.log(  Цена: ${this.price} руб.);
   console.log(  Скидка: ${this.discountPercent}%);
   console.log(  Итог: ${finalPrice.toFixed(2)} руб.);
   console.log("---");
 };
}

const products = [
 new Product("Кофе", 600, 10),
 new Product("Чай", 450, 5),
 new Product("Печенье", 150, 15),
];

products.forEach(product =>  product.logInfo());
Результат в консоли.
Результат в консоли.

Как это работает? Мы создаем шаблон (конструктор), который определяет свойства и методы будущих объектов. При каждом вызове с new создается новый экземпляр с собственной копией методов.

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

class Product {
 constructor(name, price, discountPercent) {
   this.name = name;
   this.price = price;
   this.discountPercent = discountPercent;
 }
  
  getFinalPrice() {
   const discountAmount = this.price * (this.discountPercent / 100);
   return this.price - discountAmount;
 }
  
  logInfo() {
   const finalPrice = this.getFinalPrice();
   console.log(${this.name}\n  Цена: ${this.price} руб.\n  Скидка: ${this.discountPercent}%\n  Итог: ${finalPrice.toFixed(2)} руб.\n---);
 }
}

// Использование остается точно таким же!
const products = [
 new Product("Кофе", 600, 10),
 new Product("Чай", 450, 5),
 new Product("Печенье", 150, 15),
];

products.forEach(product => product.logInfo());
Результат в консоли тот же.
Результат в консоли тот же.

Что изменилось? Код стал чище и логичнее. Все части класса собраны в одном блоке {}. Методы теперь записываются в более привычном виде, без ключевого слова function. А главное, это удобно!

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

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

Рассмотрим подробнее синтаксис класса на нашем примере:

  • class Product — объявление класса с именем (по соглашению с заглавной буквы);

  • constructor(...) — специальный метод, который вызывается при создании нового экземпляра автоматически через new. С помощью него инициализируем свойства;

  • getFinalPrice() {...} и logInfo() {...} — методы класса. Они автоматически попадают в прототип.

Мы написали свой первый класс. Теперь давайте разберемся с дополнительными возможностями, которые делают классы еще удобнее и превращают обычные свойства в защищенные интерфейсы.

Бесплатный базовый курс по JS

Рассказываем, как работать с переменными, типами данных, функциями и о многом другом!

Старт в январе →

Геттеры и сеттеры (get/set)

Эти инструменты — приятный бонус, который мы получаем при создании объектов с помощью классов. Они позволяют нам управлять доступом к свойствам объекта более гибко. Прежде чем перейти к коду, давайте разберемся с определениями.

Геттер (get) — это специальный метод класса, который позволяет получать значение свойства, но выглядит и используется как обычное свойство. При его вызове можно выполнить дополнительные вычисления или проверки перед возвратом значения.

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

Если сказать коротко, это способ создания «умных» свойств, значения которых можно обработать или проверить перед тем, как их прочитать или изменить.

Давайте добавим в наш класс Product сеттер для свойства price, который будет проверять, что устанавливаемая цена больше нуля. И также добавим геттер, который будет возвращать цену с двумя знаками после запятой (форматированную для вывода).

class Product {
 constructor(name, price, discountPercent) {
   this.name = name;
   this.price = price;
   this.discountPercent = discountPercent;
 }
  
 getFinalPrice() {
   const discountAmount = this.price * (this.discountPercent / 100);
   return this.price - discountAmount;
 }

 logInfo() {
   const finalPrice = this.getFinalPrice();
   console.log(
     `${this.name}\n  Цена: ${this.formattedPrice}\n  Скидка: ${
       this.discountPercent
     }%\n  Итог: ${finalPrice.toFixed(2)} руб.\n---`
   );
 }

 // Геттер для форматированной цены
 get formattedPrice() {
   return `${this._price.toFixed(2)} руб.`; // Используем внутреннее свойство
 }

 // Сеттер для цены с проверкой
 set price(value) {
   if (value <= 0) {
     console.error("Ошибка: цена должна быть больше нуля!");
     this._price = 1; // Установим минимальную цену
     return;
   }
   this._price = value;
 }

 // Геттер для чтения цены
 get price() {
   return this._price;
 }
}

// Давайте проверим, как это работает
const products = [
 new Product("Кофе", 600, 10),
 new Product("Чай", 450, 5),
 new Product("Печенье", 150, 15),
];

// Все работает как обычно
products.forEach((product) => product.logInfo());

// Тестируем сеттер
console.log("\n=== Тестируем защиту от некорректных цен ===");
const testProduct = new Product("Тестовый товар", -100, 10); // Отрицательная цена!
console.log(testProduct.formattedPrice); // 1.00 руб. (была исправлена)

// Меняем цену через сеттер
testProduct.price = 200;
console.log(`Новая цена: ${testProduct.formattedPrice}`); // 200.00 руб.

// Пытаемся установить недопустимое значение
testProduct.price = 0; // Ошибка в консоли
console.log(`Цена после некорректной установки: ${testProduct.formattedPrice}`); // Осталась 200.00 руб.
Результат в консоли.
Результат в консоли.

Да, с первого взгляда код может показаться сложным для понимания. Но если экспериментировать и разобраться глубже, то все проще, чем может казаться.

Как работают сеттеры и геттеры в этом примере

Когда мы создаем новый товар new Product("Кофе", 600, 10), в конструкторе происходит вызов this.price = 600. Это не прямое присваивание, а вызов сеттера set price(value)

Сеттер проверяет, что 600 > 0, и сохраняет значение во внутреннее свойство this._price. Когда нам нужно прочитать цену, например, в методе getFinalPrice(), происходит вызов геттера get price(), который просто возвращает this._price. А геттер formattedPrice преобразует число в красивую строку с надписью «руб.» и двумя знаками после запятой. 

Таким образом, мы добавили защиту от некорректных данных и удобное форматирование, не меняя основной логики работы с объектом.

Итог

Классы — это обертка, которая делает наш код чище, понятнее и структурированнее. Мы создали классы с помощью class, инициализировали свойства в конструкторе и объявили методы в едином блоке, что сильно упростило чтение и поддержку кода по сравнению с разрозненными конструкторами. А это уже важный шаг от функций-конструкторов к современному синтаксису классов в JS.

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

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

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

Сталкивались ли вы уже с наследованием в реальных задачах или пока обходитесь базовой структурой? Пишите о своем опыте и с какими вызовами вы сталкиваетесь в комментариях.