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.
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("Кафе");Стъпка по стъпка, с думи
- #nextId е static: едно поле в класа, а не във всеки обект.
- new Product("Хляб"): нов обект, конструкторът записва името.
- this.#id = Product.#nextId: обектът взима текущия номер, 1.
- Product.#nextId++ сменя общия брояч: вече е 2.
- Мляко: същият брояч. Взима 2 и го прави 3.
- Кафе: взима 3 и го прави 4.
- Броячът е един за всички, затова номерата са поредни.
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);count е static: един брояч за всички поръчки, увеличен три пъти. 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));305.258На Calculator.add не му трябва обект: всичко, от което има нужда, идва като параметри. Такива са и методите на Math: Math.max, Math.sqrt. От Math обекти не се правят и new Math() спира програмата:
TypeError: Math is not a constructorJavaScript няма main: програмата е редовете на файла отгоре надолу. Затова функциите от модул „Функции“ нямаха нужда от клас.
По-точно, за любопитните
Обратното също работи: в static метод можеш да създадеш обект и да викаш неговите методи.
Пробвай
Намери грешката
Избери реда с грешката.
label() чете this.#name, а името е на конкретен продукт. static метод е на класа, затова обектът tea изобщо няма такъв метод:TypeError: tea.label is not a functionstatic: етикетът е на продукта, затова е метод на обекта.Идеята
Константи
ДДС е 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.
Пробвай
Кое липсва?
MONTHS = 12;const: стойността се задава веднъж и не може да се смени.Идеята
Кога 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 €Поръчки: 32. Калкулатор. Клас 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-5422.53. Обмяна. Клас Converter с константи за курса: 1 € = 1,08 щатски долара и 1 € = 0,85 британски лири (курсовете са примерни). Static методи превръщат сума в евро в долари и в лири. За 20 €:
20 EUR = 21.6 USD20 EUR = 17 GBPОще задачи
- Сметки с номера. Към
Accountот урока „Капсулация“ добави пореден номер на сметката от static брояч и getter без setter за номера. - Обмяна в двете посоки. Добави към
Converterметоди от долари и от лири към евро. Превърни 20 € в долари и обратно в евро. Получава ли се точно 20? - Ограничение. Магазинът приема най-много 3 продукта в количка. Сложи константа за това и накарай метода за добавяне да отказва четвъртия.
Накратко
Модулът накратко
- Клас е твой тип, обект е конкретен екземпляр по него, създаден с
new. Променливата сочи към обекта. Свой тип constructorзадължава обектът да се създаде цял.thisе текущият обект. Конструктори и this- Методите на обекта работят с неговите полета.
toString()казва как изглежда като текст. Поведение #полетата са частни. Отвън се стига до тях само презgetиset, които проверяват. Капсулацияstaticе общото за класа: броячи и помощни методи. Константите саconst.
Данни и правила на едно място, които никой не може да заобиколи: това е обектно-ориентираното програмиране. В следващия модул класовете ще започнат да споделят код помежду си.
Тест