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

Свой тип

Проблем

Цените се разбъркаха

Пишеш количката на онлайн магазин. Всеки продукт има име и цена, затова ги пазиш в два масива: на индекс 0 са името и цената на първия продукт, на индекс 1 на втория.

Искаш най-скъпото най-отпред и разменяш цените:

const names = ["Хляб", "Мляко", "Кафе"];
const prices = [1.20, 2.10, 7.50];
// Най-скъпото отпред: разменяме първата и третата цена.
const temp = prices[0];
prices[0] = prices[2];
prices[2] = temp;
for (let i = 0; i < names.length; i++) {
console.log(`${names[i]}: ${prices[i]} €`);
}
Изход
Хляб: 7.5 €
Мляко: 2.1 €
Кафе: 1.2 €

Хлябът вече струва 7,50 €. Цените са разменени, а имената не са. Двата масива трябва да се пипат винаги заедно, но нищо не те задължава. Добавиш ли трети масив с бройките, ще трябва да помниш три.

Идеята

Един продукт, едно място

Името и цената не са отделни неща. Те са части от един продукт. Искаме тип „продукт“, както има тип number за число и string за текст.

Такъв тип си правиш сам. Той се казва клас:

class Product {
name;
price;
quantity;
}

Променливите в класа са неговите полета. Всеки продукт ще има свое име, своя цена и своя бройка.

Класът е само описание: „всеки продукт има име, цена и бройка“. Продукт още няма.

Името на класа започва с главна буква (Product), а имената на полетата с малка (name, price). Така веднага се вижда кое е тип и кое е стойност.

Идеята

new: от описанието към продукт

Конкретен продукт по класа се създава с new. Той се казва обект. До полетата му стигаш с точка:

shop.js
class Product {
name;
price;
quantity;
}
const bread = new Product();
bread.name = "Хляб";
bread.price = 1.20;
bread.quantity = 2;
const coffee = new Product();
coffee.name = "Кафе";
coffee.price = 7.50;
coffee.quantity = 1;
console.log(`${bread.name}: ${bread.price * bread.quantity} €`);
console.log(`${coffee.name}: ${coffee.price * coffee.quantity} €`);
Изход
Хляб: 2.4 €
Кафе: 7.5 €
  • Класът стои над кода, който го ползва. Във файла може да има няколко класа.
  • new Product() създава нов обект. Всеки обект има свои полета: хлябът и кафето не си пречат.
  • bread.price е „цената на хляба“. Чете се и се записва като обикновена променлива.
  • const bread значи, че bread винаги сочи същия обект. Полетата на обекта пак може да се сменят.

Сега името и цената на продукта са на едно място. Разместиш ли продукта, те вървят заедно.

Пробвай

Нищо не е попълнено

Ако създадеш продукт и не попълниш нищо, какво има в полетата?

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

const item = new Product();
console.log(item.name);
console.log(item.price);
console.log(item.quantity);
Отговор

Пробвай

Кое липсва?

Какво трябва да пише на празното място, за да има нов продукт?

const milk =  Product();
milk.name = "Мляко";
milk.price = 2.10;

Виж го

Променливата сочи към обекта

Искаш промоция на кафето и правиш „копие“, за да му смениш цената:

const coffee = new Product();
coffee.price = 10;
const sale = coffee;
sale.price = 8;
console.log(coffee.price); // 8
Стъпка по стъпка, с думи
  1. new прави обекта: полетата му още са undefined. В coffee отива стрелка към него.
  2. По стрелката от coffee: цената на обекта става 10.
  3. sale = coffee копира стрелката, не обекта. Две стрелки, един обект.
  4. По стрелката от sale: цената в същия обект става 8.
  5. coffee сочи същия обект, затова печата 8.

Цената на кафето също стана 8. Причината е, че променливата coffee не е самият обект. Обектът стои отделно в паметта, а coffee пази къде е той: стрелка към обекта. Такава променлива се казва референция.

sale = coffee копира стрелката, а не обекта. Сега две стрелки сочат един обект, затова промяната през sale се вижда и през coffee.

Същото видя в урока за масивите: sale = prices даде второ име на същия масив. Масивът също е обект.

Идеята

Отстъпката вече работи

В модул „Функции“ отстъпката не се получи: функцията получи цената, смени своя параметър и промяната изчезна. С обект е друго:

function discount(p, percent) {
p.price = p.price * (100 - percent) / 100;
}
const coffee = new Product();
coffee.price = 10;
discount(coffee, 10);
console.log(coffee.price);
Изход
9

Параметърът пак е копие, но копие на стрелката. Тя сочи същия обект, затова промяната на p.price остава и след функцията.

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

Ако функцията сложи в параметъра нов обект, тя мести само своето копие на стрелката. Обектът отвън не се променя:

function reset(p) {
p = new Product();
p.price = 0;
}
const coffee = new Product();
coffee.price = 10;
reset(coffee);
console.log(coffee.price);
Изход
10
Затова се казва, че JavaScript предава всичко по стойност, а при обектите стойността е референцията.

Идеята

null: стрелка за никъде

Променлива може да не сочи към никакъв обект. Тогава в нея е null: „нищо“. Ако опиташ да прочетеш поле през нея, няма обект, от който да го прочетеш:

const gift = null;
console.log(gift.name);

Програмата тръгва, но спира с грешка, когато стигне до реда:

Грешка
TypeError: Cannot read properties of null (reading 'name')

Това е най-честата грешка с обекти. Какво е „exception“ и как се хваща, ще видим в модул „Грешки и изключения“.

Масивът също пази стрелки, а не самите продукти. new Array(3).fill(null) прави три места, които още не сочат към продукт. Всяко е null, докато не сложиш там new Product():

const cart = new Array(3).fill(null);
console.log(cart[0] === null);
cart[0] = new Product();
cart[0].name = "Хляб";
console.log(cart[0].name);
Изход
true
Хляб

Пробвай

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

Програмата трябва да отпечата „Хляб“ и „Мляко“. Вместо това печата „Мляко“ два пъти. На кой ред е грешката?

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

Упражнение

Твоите класове

Реши задачите в редактора си. Класът стои най-горе във файла, а обектите се създават в кода под него.

1. Книга. Клас Book с полета за заглавие и автор. Създай две книги и ги отпечатай.

Изход
Под игото от Иван Вазов
Тютюн от Димитър Димов

2. Кола. Клас SimpleCar с поле за скоростта. Създай кола (скоростта ѝ е 0), увеличи скоростта с 10 и я отпечатай. Дай на полето начална стойност (speed = 0;): без нея undefined + 10 дава NaN, „не е число“. В урока „Поведение“ това ще стане метод на колата.

Изход
Скорост след ускоряване: 10

3. Количката. Количката от първата карта, този път с масив от три обекта Product: хляб 1,20 € (2 броя), мляко 2,10 € (1 брой), кафе 7,50 € (1 брой). Отпечатай сумата за всеки ред и общата сума. Всеки продукт първо се създава с new Product(), а после влиза в масива.

Изход
Хляб x2: 2.4 €
Мляко x1: 2.1 €
Кафе x1: 7.5 €
Общо: 12 €

4. Най-скъпото отпред. Подреди същата количка по цена, от най-скъпото към най-евтиното, с метода на мехурчето от урока за масивите. Този път разменяш цели продукти, затова името и цената вървят заедно.

Изход
Кафе: 7.5 €
Мляко: 2.1 €
Хляб: 1.2 €

Още задачи

  • Ученик. Клас Student с име и три оценки (три полета). Създай двама ученици и отпечатай средния успех на всеки.
  • Една книга, две имена. Създай книга, запиши я в две променливи и смени заглавието през втората. Отпечатай заглавието през първата. Обясни с думи защо е така.

Тест

Провери се

1. Каква е разликата между клас и обект?
2. const sale = coffee; sale.price = 8; Какво става с цената на coffee?
3. Какво има в масива веднага след const cart = new Array(3).fill(null);?
4. Функция получава продукт като параметър и му сменя цената. Вижда ли се промяната след извикването?
5. const gift = null; Какво става при gift.name?

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