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

Масив, който расте

Проблем

Количката е пълна

Пишеш количката на онлайн магазин и пазиш продуктите в масив, както в урока за масивите: всеки продукт на своя индекс. JavaScript позволява да пишеш и след края, но кой е първият свободен индекс, следиш ти. А когато клиентът махне продукт, остава дупка:

const cart = ["Хляб", "Мляко", "Кафе"];
// Четвърти продукт: пишем на първия свободен индекс.
cart[cart.length] = "Масло";
// Клиентът маха млякото.
cart[1] = undefined;
for (const item of cart) {
console.log(item);
}
console.log(cart.length);
Изход
Хляб
undefined
Кафе
Масло
4
Сега всеки цикъл по количката трябва да прескача undefined. А броят продукти вече не е cart.length: той е 4, а продуктите са 3.

Това е много код за нещо, което всеки магазин прави постоянно.

Идеята

push и splice: масивът се грижи сам

Масивът има методи, които правят това вместо теб. Копирането и местенето прави сам. Колекция, която расте и се свива така, се казва списък, а в JavaScript това е самият масив:

const cart = [];
cart.push("Хляб");
cart.push("Мляко");
cart.push("Кафе");
cart.push("Масло");
cart.splice(cart.indexOf("Мляко"), 1);
console.log(cart);
console.log(`Продукти: ${cart.length}`);
Изход
[ 'Хляб', 'Кафе', 'Масло' ]
Продукти: 3
  • push добавя в края.
  • Метод, който маха по стойност, няма. indexOf намира индекса на „Мляко“, а splice(индекс, 1) маха един елемент оттам и затваря дупката: „Кафе“ се мести на мястото на „Мляко“.
  • length е колко елемента има в момента.
  • console.log печата целия масив в квадратни скоби, а текста в кавички.

Ето какво прави масивът вътре. Колко места има, не избираш ти, а двигателят, който изпълнява кода (V8 в Node и Chrome). Масив от 3 продукта в кода има точно 3 места, затова се пълни на четвъртия:

const cart = ["Хляб", "Мляко", "Кафе"];
cart.push("Масло");
cart.splice(cart.indexOf("Мляко"), 1);
console.log(cart);
console.log(`Продукти: ${cart.length}`);
Стъпка по стъпка, с думи
  1. cart сочи масив. Отвътре той има length и отделен масив elements с 3 места.
  2. Продуктите от кода заемат местата един по един. Масивът е пълен: 3 от 3.
  3. Няма място. push прави масив с 22 места и копира елементите в него.
  4. elements вече сочи новия масив, а старият не трябва на никого. „Масло“ влиза на свободното място.
  5. splice маха „Мляко“, а всичко след него се мести с едно място наляво. Дупка не остава.
  6. Отвън се вижда само масив с три продукта. Колко места има отвътре, решава двигателят V8: езикът не го обещава.
По-точно, за любопитните
  • Има и delete cart[1], но то не мести нищо. Оставя дупка, а length остава същият:
const cart = ["Хляб", "Мляко", "Кафе"];
delete cart[1];
console.log(cart, cart.length);
Изход
[ 'Хляб', <1 empty item>, 'Кафе' ] 3

За масив ползвай splice.

Идеята

Какво има в масива

Масивът не пита какво слагаш в него. Текст и число могат да стоят едно до друго:

const cart = [];
cart.push("Хляб");
cart.push(5);
console.log(cart);
Изход
[ 'Хляб', 5 ]

Това изглежда удобно, но грешката излиза по-късно, там, където очакваш текст. Затова един масив пази един вид неща: само продукти, само цени.

В самия JavaScript няма как да запишеш „масив само от текст“. Това прави TypeScript, JavaScript с типове: там масивът от текст е string[] и cart.push(5) е грешка още в редактора.

Идеята

Индекси: [i], splice, indexOf, includes

Масивът има индекси от 0 до tasks.length - 1. Четеш и заменяш с [ ], а вмъкваш със splice:

const tasks = ["Фактура за март", "Обаждане до банката", "Отчет за склада"];
console.log(tasks[0]);
tasks[1] = "Среща в банката";
tasks.splice(0, 0, "Кафе с екипа");
console.log(tasks.join(", "));
console.log(tasks.indexOf("Отчет за склада"));
console.log(tasks.includes("Обаждане до банката"));
Изход
Фактура за март
Кафе с екипа, Фактура за март, Среща в банката, Отчет за склада
3
false
  • tasks[i] чете, tasks[i] = x заменя.
  • splice(i, 0, x) вмъква на индекс i и мести останалите надясно. Нулата значи „не махай нищо“.
  • indexOf(x) търси и връща индекса или -1, а includes(x) казва само дали го има.
  • join(", ") слепва елементите в един текст, разделени със запетая.
По-точно, за любопитните
  • Индекс извън масива не спира програмата, а дава undefined. at(-1) брои отзад и дава последния елемент:
const tasks = ["Фактура за март", "Обаждане до банката", "Отчет за склада"];
console.log(tasks[3]);
console.log(tasks.at(-1));
console.log(tasks.indexOf("Заплати"));
Изход
undefined
Отчет за склада
-1
  • includes и indexOf сравняват с ===. Текстът е стойност, затова намират текст, който е равен.
  • Вмъкването и махането отпред са бавни при голям масив: всички елементи след индекса се местят с едно място. За един милион елемента това е един милион премествания.

Пробвай

Кой е на ред?

Списъкът с чакащите при лекаря. Иван влиза, а Ана има час и се нарежда втора.

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

const waiting = ["Иван", "Мария", "Петър"];
waiting.shift();
waiting.splice(1, 0, "Ана");
console.log(waiting, waiting.length);
Отговор

Идеята

Обхождане

Масивът се обхожда с for...of, когато ти трябват само елементите, или с индекс, когато ти трябва и номерът:

const cart = ["Хляб", "Кафе", "Масло"];
for (const item of cart) {
console.log(`- ${item}`);
}
for (let i = 0; i < cart.length; i++) {
console.log(`${i + 1}. ${cart[i]}`);
}
Изход
- Хляб
- Кафе
- Масло
1. Хляб
2. Кафе
3. Масло
По-точно, за любопитните
  • Не махай елементи от масива, докато го обхождаш с for...of. Програмата не спира, но прескача елементи:
const cart = ["Хляб", "Хляб", "Мляко"];
for (const item of cart) {
if (item === "Хляб") {
cart.splice(cart.indexOf(item), 1);
}
}
console.log(cart);
Изход
[ 'Хляб', 'Мляко' ]

Вторият „Хляб“ остана. splice премести всичко наляво, а цикълът продължи от следващия индекс и не го видя. Грешка няма, затова такъв пропуск се хваща трудно.

  • Ако трябва да махаш, обхождай с индекс отзад напред. Така махането не мести елементите, които още не си проверил:
const cart = ["Хляб", "Хляб", "Мляко"];
for (let i = cart.length - 1; i >= 0; i--) {
if (cart[i] === "Хляб") {
cart.splice(i, 1);
}
}
console.log(cart);
Изход
[ 'Мляко' ]
В модул „Модерен JavaScript“ ще видиш по-кратък начин, който прави нов масив само с елементите, които искаш: filter.

Идеята

Числа в масив

Масив от оценки е като масив от текст. Числото стои в масива, както си е:

const scores = [85, 64, 91];
let total = 0;
for (const score of scores) {
total += score;
}
console.log(`Средно: ${total / scores.length}`);
Изход
Средно: 80

JavaScript печата 80, а не 80.0: за него цялото и дробното число са един и същ тип, number.

По-точно, за любопитните
  • Числото в JavaScript е проста стойност, не обект. Затова === сравнява стойностите и две равни числа са равни. Има и обвивка new Number(257), която прави обект, а два обекта са различни, дори да пазят едно и също:
const c = Number("257");
const d = Number("257");
console.log(c === d);
console.log(new Number(257) === new Number(257));
Изход
true
false

new Number не ти трябва. Пиши числата направо.

Пробвай

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

Залата пази заетите места в масив. Клиентът се отказва от място 2 и програмата трябва да отпечата [ 12, 7, 5 ]. Вместо това печата [ 12, 2, 5 ]. На кой ред е грешката?

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

По-точно, за любопитните
const seats = [12, 7, 5];
seats.splice(seats.indexOf(2), 1);
console.log(seats);
Изход
[ 12, 7 ]

Идеята

Подреждане и масив, който не се променя

sort() подрежда масива на място. Текстът излиза по азбучен ред. Числата обаче не излизат по големина:

const cities = ["София", "Пловдив", "Варна"];
cities.sort();
console.log(cities);
const prices = [30, 5, 12];
prices.sort();
console.log(prices);
prices.sort(function (a, b) {
return a - b;
});
console.log(prices);
Изход
[ 'Варна', 'Пловдив', 'София' ]
[ 12, 30, 5 ]
[ 5, 12, 30 ]

Без правило sort превръща всичко в текст и сравнява текста. „12“ е преди „30“, а „30“ преди „5“, както в речника. Затова на числата даваш правило: функция, която получава две числа и връща a - b. Отрицателен резултат значи „a е преди b“. Засега я копирай, а в урока „Класове в класове“ ще видиш как се пишат такива правила.

Масив, който не се променя, се прави с Object.freeze. Той е неизменим: не можеш да добавяш, махаш или подреждаш в него.

const days = Object.freeze(["пн", "вт", "ср"]);
days.push("чт");
Грешка
TypeError: Cannot add property 3, object is not extensible

const не стига за това: той само не дава на days да сочи друг масив, а самият масив може да се променя.

По-точно, за любопитните
  • Текстът се подрежда по кодовете на символите. Главните букви са преди малките, затова „Ябълка“ е преди „ананас“:
const fruit = ["ананас", "Ябълка", "банан", "Круша"];
fruit.sort();
console.log(fruit);
Изход
[ 'Круша', 'Ябълка', 'ананас', 'банан' ]
  • toSorted() връща нов, подреден масив и не пипа стария:
const prices = [30, 5, 12];
const cheapFirst = prices.toSorted(function (a, b) {
return a - b;
});
console.log(cheapFirst, prices);
Изход
[ 5, 12, 30 ] [ 30, 5, 12 ]
  • Как се подреждат твои обекти, например продукти по цена, ще видиш в урока „Класове в класове“.

Упражнение

Масиви в класове

Реши задачите в редактора си. Масивът е частно поле на класа (#books), а кодът под класовете вика само методите му.

1. Рафт с книги. Клас BookShelf с методи addBook(title), който слага книгата в края, и takeBook(), който връща и маха първата книга (или null, ако рафтът е празен). Добави count() и toString(). Сложи „Под игото“, „Тютюн“ и „Железният светилник“, вземи една книга и отпечатай останалите.

Изход
Взе: Под игото
Остават 2: [Тютюн, Железният светилник]

2. Журнал на събитията. Клас Event с дата и описание и клас EventLog с методи addEvent(date, description) и getEventsBetween(from, to), който връща масив със събитията между двете дати, включително. Датите са текст във вида 2026-03-31. В този вид текстът се подрежда като датите, затова <= и >= ги сравняват вярно. Добави четири събития: 2026-01-15 „Годишна инвентаризация“, 2026-03-31 „Отчет за първото тримесечие“, 2026-05-20 „Нов доставчик на кафе“, 2026-09-01 „Нов учебен сезон“. Отпечатай тези от 2026-03-01 до 2026-06-30.

Изход
2026-03-31 Отчет за първото тримесечие
2026-05-20 Нов доставчик на кафе

3. Тетрадка. Клас Note със заглавие и съдържание и клас Notebook с методи addNote, findNote(title) (връща бележката или null), deleteNote(title) (връща true, ако е изтрил) и count(). Добави бележки „ООП“, „Колекции“ и „SQL“, намери „Колекции“, изтрий „ООП“ и провери, че вече я няма.

Изход
Колекции: масивът расте сам
Изтрита: ООП
Бележки: 2
Няма бележка ООП.

4. Смятане наум. Клас Question с текст и верен отговор и клас MathGame с масив от въпроси, метод addQuestion и метод start(). start задава въпросите един по един, чете отговора и накрая казва колко са верните. Въпросите са 2 + 2, 3 * 4 и 15 - 9.

start чете с ask от урока за входа, затова е async start(), а извикването е await game.start(). Метод, който чака вход, се пише така. Засега го копирай.

Вход
4
12
7
Изход
2 + 2 = Вярно!
3 * 4 = Вярно!
15 - 9 = Грешно, отговорът е 6.
Резултат: 2 от 3

Още задачи

  • Без повторения. Прочети 6 имена и ги събери в масив, като пропускаш тези, които вече са в него. При вход Иван, Мария, Иван, Ана, Мария, Петър изходът е [ 'Иван', 'Мария', 'Ана', 'Петър' ].
  • Най-скъпият продукт. В масив има цени. Намери най-високата и на кой индекс е, без Math.max.
  • Обърни списъка. Напиши функция, която връща нов масив със същите елементи в обратен ред. Не променяй първоначалния.
  • Списък за пазаруване. Чети команди add мляко, remove мляко, list и end и пази списъка между тях. Командата за триене на продукт, който го няма, казва „Няма такъв продукт.“

Тест

Провери се

1. С какво push и splice са по-удобни от писането по индекс?
2. Какво отпечатва console.log([30, 5, 12].sort())?
3. seats е [12, 2, 7, 5]. Какво остава след seats.splice(2, 1)?
4. В tasks има 3 задачи. Какво отпечатва console.log(tasks[3])?
5. const days = Object.freeze(["пн", "вт"]); Какво става при days.push("ср")?

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