Към съдържанието
wannacode

static: общото за всички

Проблем

Всеки продукт е №1

Магазинът иска всеки нов продукт да получава пореден номер: 1, 2, 3… Слагаш брояч #nextId в класа. Конструкторът дава на продукта текущия номер и увеличава брояча:

class Product {
#nextId = 1;
#id;
#name;
constructor(name) {
this.#name = name;
this.#id = this.#nextId;
this.#nextId++;
}
toString() {
return `№${this.#id} ${this.#name}`;
}
}
const bread = new Product("Хляб");
const milk = new Product("Мляко");
const coffee = new Product("Кафе");
console.log(String(bread));
console.log(String(milk));
console.log(String(coffee));
Изход
№1 Хляб
№1 Мляко
№1 Кафе

Всички са №1. Причината е, че #nextId е поле на обекта, както името. Всеки нов продукт получава свой брояч, който започва от 1. Броячът на хляба никога не стига до млякото.

Идеята

Поле на класа, не на обекта

Броячът не е част от един продукт. Той е общ за всички продукти. Добави static:

static #nextId = 1;

static поле е поле на класа. То е едно, колкото и обекти да създадеш, и всички обекти го делят. В конструктора до него се стига през името на класа: Product.#nextId.

shop.js
class Product {
static #nextId = 1;
#id;
#name;
constructor(name) {
this.#name = name;
this.#id = Product.#nextId;
Product.#nextId++;
}
toString() {
return `№${this.#id} ${this.#name}`;
}
}
const bread = new Product("Хляб");
const milk = new Product("Мляко");
const coffee = new Product("Кафе");
console.log(String(bread));
console.log(String(milk));
console.log(String(coffee));
Изход
№1 Хляб
№2 Мляко
№3 Кафе

Хлябът взима 1 и прави брояча 2. Млякото вижда същия брояч, взима 2 и го прави 3.

static #nextId = 1;
constructor(name) {
this.#name = name;
this.#id = Product.#nextId;
Product.#nextId++;
}
const bread = new Product("Хляб");
const milk = new Product("Мляко");
const coffee = new Product("Кафе");
Стъпка по стъпка, с думи
  1. #nextId е static: едно поле в класа, а не във всеки обект.
  2. new Product("Хляб"): нов обект, конструкторът записва името.
  3. this.#id = Product.#nextId: обектът взима текущия номер, 1.
  4. Product.#nextId++ сменя общия брояч: вече е 2.
  5. Мляко: същият брояч. Взима 2 и го прави 3.
  6. Кафе: взима 3 и го прави 4.
  7. Броячът е един за всички, затова номерата са поредни.

static полето не се вижда през обект. Отвън до него се стига през името на класа (Order.count):

class Order {
static count = 0;
}
const order = new Order();
console.log(Order.count, order.count);
Изход
0 undefined

Пробвай

Общо и свое

Какво ще отпечата програмата?

class Order {
static count = 0;
items = 0;
constructor() {
Order.count++;
this.items++;
}
}
const a = new Order();
const b = new Order();
const c = new Order();
console.log(Order.count, c.items);
Отговор

Идеята

static метод: работа без обект

Същото важи за методите. static метод е метод на класа. Вика се през името на класа, без new:

class Calculator {
static add(a, b) {
return a + b;
}
static average(values) {
let sum = 0;
for (const v of values) {
sum += v;
}
return sum / values.length;
}
}
console.log(Calculator.add(10, 20));
const grades = [5, 6, 4, 6];
console.log(Calculator.average(grades));
console.log(Math.max(3, 8));
Изход
30
5.25
8

На Calculator.add не му трябва обект: всичко, от което има нужда, идва като параметри. Такива са и методите на Math: Math.max, Math.sqrt. От Math обекти не се правят и new Math() спира програмата:

Грешка
TypeError: Math is not a constructor

JavaScript няма main: програмата е редовете на файла отгоре надолу. Затова функциите от модул „Функции“ нямаха нужда от клас.

По-точно, за любопитните

Обратното също работи: в static метод можеш да създадеш обект и да викаш неговите методи.

Пробвай

Намери грешката

tea.label() спира програмата. На кой ред е грешката?

Избери реда с грешката.

Идеята

Константи

ДДС е 20% за всички продукти и не се променя, докато програмата работи. Такава стойност е константа:

const VAT_RATE = 0.20;
class Prices {
static withVat(price) {
return price * (1 + VAT_RATE);
}
}
console.log(Prices.withVat(50));
console.log(`ДДС: ${VAT_RATE * 100}%`);
Изход
60
ДДС: 20%

const значи, че променливата се задава веднъж и не може да се смени. Слагаш я извън класа, в началото на файла. Имената на константите са с главни букви и долна черта: VAT_RATE.

Ако някой опита да я смени,

VAT_RATE = 0.25;

програмата спира:

Грешка
TypeError: Assignment to constant variable.

Ползата е двойна: числото 0.20 е написано на едно място с ясно име, и никой не може да го промени по погрешка.

По-точно, за любопитните

static поле в класа не е константа: отвън може да се смени.

class Prices {
static VAT_RATE = 0.20;
}
Prices.VAT_RATE = 0.25;
console.log(Prices.VAT_RATE);
Изход
0.25

Понякога стойността е различна за всеки обект, но след създаването не трябва да се мени. Такъв е номерът на продукта. Направи го частно поле с getter без setter (урок „Капсулация“):

class Product {
static #nextId = 1;
#id;
#name;
constructor(name) {
this.#name = name;
this.#id = Product.#nextId;
Product.#nextId++;
}
get id() {
return this.#id;
}
}

coffee.id чете номера, а coffee.id = 5 не го сменя: полето #id е частно и има само getter.

Пробвай

Кое липсва?

Месеците в годината са 12 и това няма да се промени. Какво пише на празното място?
 MONTHS = 12;

Идеята

Кога static и кога не

Питай се: „Това на конкретен обект ли е?“

  • Да (балансът на сметката, името на продукта, тегленето от сметка): поле или метод на обекта, без static.
  • Не (броят на всички сметки, ДДС, сбор на две числа): static.

Честа грешка е всичко да стане static, за да не правиш обекти. Тогава всички „обекти“ делят едни и същи данни и се връщаш при проблема с успоредните масиви от първия урок.

Упражнение

Общото за всички

Реши задачите в редактора си.

1. Поръчки. Клас Order с клиент и сума. Всяка нова поръчка получава пореден номер от static брояч. Static getter count казва колко поръчки са създадени.

Програмата
const orders = [
new Order("Мария", 45.5),
new Order("Петър", 120),
new Order("Елена", 18.9),
];
for (const o of orders) {
console.log(String(o));
}
console.log(`Поръчки: ${Order.count}`);
Изход
Поръчка №1: Мария, 45.5 €
Поръчка №2: Петър, 120 €
Поръчка №3: Елена, 18.9 €
Поръчки: 3

2. Калкулатор. Клас Calculator със static методи за събиране, изваждане и умножение на цели числа и за деление на дробни.

Програмата
console.log(Calculator.add(10, 20));
console.log(Calculator.subtract(10, 15));
console.log(Calculator.multiply(6, 7));
console.log(Calculator.divide(5, 2));
Изход
30
-5
42
2.5

3. Обмяна. Клас Converter с константи за курса: 1 € = 1,08 щатски долара и 1 € = 0,85 британски лири (курсовете са примерни). Static методи превръщат сума в евро в долари и в лири. За 20 €:

Изход
20 EUR = 21.6 USD
20 EUR = 17 GBP

Още задачи

  • Сметки с номера. Към Account от урока „Капсулация“ добави пореден номер на сметката от static брояч и getter без setter за номера.
  • Обмяна в двете посоки. Добави към Converter методи от долари и от лири към евро. Превърни 20 € в долари и обратно в евро. Получава ли се точно 20?
  • Ограничение. Магазинът приема най-много 3 продукта в количка. Сложи константа за това и накарай метода за добавяне да отказва четвъртия.

Накратко

Модулът накратко

  • Клас е твой тип, обект е конкретен екземпляр по него, създаден с new. Променливата сочи към обекта. Свой тип
  • constructor задължава обектът да се създаде цял. this е текущият обект. Конструктори и this
  • Методите на обекта работят с неговите полета. toString() казва как изглежда като текст. Поведение
  • # полетата са частни. Отвън се стига до тях само през get и set, които проверяват. Капсулация
  • static е общото за класа: броячи и помощни методи. Константите са const.

Данни и правила на едно място, които никой не може да заобиколи: това е обектно-ориентираното програмиране. В следващия модул класовете ще започнат да споделят код помежду си.

Тест

Провери се

1. Създадени са 5 обекта от клас с поле static count = 0. Колко броя count има в паметта?
2. Кое НЕ може static метод?
3. Order има static count = 0. Какво дава order.count за обект order?
4. Какво става при VAT_RATE = 0.25;, ако VAT_RATE е обявена с const?
5. Кое е добър кандидат за static метод?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·