Angularшлях до Strong Junior
Модулі / JavaScript: основа / Масиви та їхні методи
🔥 0

Фундамент Ф1 · урок 5 з 12

Масиви та їхні методи

Масив — впорядкований список значень. Майже всі дані в застосунку — масиви обʼєктів: задачі, користувачі, рядки таблиці, пункти меню. А методи масивів — map, filter, find — найчастіший код в Angular: @for у шаблоні, computed зі списком, обробка відповіді сервера. Це найважливіший урок модуля, не поспішай.

Основи: індекс і довжина

const tasks = ['Звіт', 'Дизайн', 'Реліз'];

tasks[0];                  // 'Звіт' — нумерація з нуля!
tasks[2];                  // 'Реліз'
tasks[3];                  // undefined — такого елемента немає
tasks.length;              // 3
tasks[tasks.length - 1];   // 'Реліз' — останній
tasks.at(-1);              // те саме, коротше
tasks.includes('Дизайн');  // true — чи є такий елемент

Індекси починаються з нуля, тому останній елемент — length - 1. Звернення за межі масиву не дає помилки, а мовчки повертає undefined.

Змінити масив: два світи

Одні методи змінюють сам масив — кажуть, «мутують». Інші повертають новий, не чіпаючи старого. Цю різницю треба знати напамʼять: в Angular мутація — джерело багів «дані змінились, а екран ні». Чому так — урок 6.

Мутують ⚠️Повертають новий ✓
push(x) — додати в кінець[...list, x] — новий масив з x у кінці
unshift(x) — додати на початок[x, ...list]
pop(), shift() — забрати з кінця чи початкуlist.slice(0, -1), list.slice(1)
splice(i, n) — вирізатиlist.filter(…) — лишити все, крім…
sort(), reverse()toSorted(), toReversed()

Три крапки ...list — це spread: «розклади тут усі елементи списку». Детально — в уроці 6.

Перебір

for (const task of tasks) {
  console.log(task);
}

tasks.forEach((task, index) => {
  console.log(index, task);        // колбек отримує елемент і його номер
});

forEach — це метод, який викликає твій колбек для кожного елемента. Нічого не повертає: він для дій, а не для обчислень. Для обчислень є трійка нижче.

map, filter, find: трійка, без якої ніяк

Візьмемо реалістичні дані — масив обʼєктів:

const tasks = [
  { id: 1, title: 'Звіт',   done: false, hours: 3 },
  { id: 2, title: 'Дизайн', done: true,  hours: 5 },
  { id: 3, title: 'Реліз',  done: false, hours: 8 },
];

filter — відібрати. Викликає колбек для кожного елемента й лишає ті, для яких він повернув truthy. Елементів може стати менше.

tasks.filter(t => !t.done);         // дві задачі: Звіт і Реліз

map — перетворити. Повертає новий масив тієї самої довжини, де кожен елемент — результат колбека.

tasks.map(t => t.title);            // ['Звіт', 'Дизайн', 'Реліз']

find — знайти перший, що підходить. Повертає сам елемент, а не масив, або undefined, якщо нічого не знайшов.

tasks.find(t => t.id === 2);        // { id: 2, title: 'Дизайн', … }
tasks.find(t => t.id === 99);       // undefined

Їх зручно зʼєднувати в ланцюжок: кожен крок отримує результат попереднього.

Поекспериментуй: відбери лише виконані задачі, виведи години кожної, знайди задачу з id: 3.

Ще кілька корисних

  • some(fn) — чи є хоч один такий? tasks.some(t => t.done) → true.
  • every(fn) — чи всі такі? tasks.every(t => t.done) → false.
  • findIndex(fn) — номер першого такого або -1.
  • join(', ') — склеїти в рядок: ['a', 'b'].join(', ') → 'a, b'.
  • У рядків теж є методи: 'звіт'.toUpperCase() → 'ЗВІТ', ' так '.trim() → 'так', 'Звіт'.includes('ві') → true.

Виведи масив назв невиконаних задач, на які треба більше 2 годин, — великими літерами. Одним console.log.

tasks.filter(t => !t.done && t.hours > 2).map(t => t.title.toUpperCase())

reduce: згорнути в одне значення

reduce проходить масив і накопичує результат: суму, максимум, обʼєкт-групування.

const totalHours = tasks.reduce((sum, t) => sum + t.hours, 0);   // 16
//                              ↑ накопичене ↑ поточний          ↑ з чого почати

Читай так: «почни з нуля; для кожної задачі додай її години до суми». Якщо reduce поки здається заплутаним — нічого страшного: у більшості випадків вистачає map, filter і звичайного циклу.

Порахуй, скільки годин лишилось на невиконані задачі.

Спершу filter, потім reduce((sum, t) => sum + t.hours, 0).

sort: дві пастки

[10, 9, 1].sort();                       // [1, 10, 9] — сортує як рядки!
[10, 9, 1].sort((a, b) => a - b);        // [1, 9, 10] — числа порівнюють функцією

names.sort((a, b) => a.localeCompare(b, 'uk'));   // рядки — з урахуванням абетки

Перша пастка: без функції-порівняння sort перетворює все на рядки й порівнює посимвольно. Друга: sort мутує масив. Щоб не чіпати оригінал — [...list].sort(…) або новіший list.toSorted(…).

Вгадай кожен рядок, потім запусти. Особливо цікаві імена: подивись, куди потрапить «Єва» без localeCompare.

Типові помилки
  • list[list.length] — це вже за межами: останній — list.length - 1.
  • tasks.map(t => { t.title }) — фігурні дужки без return, вийде масив з undefined.
  • Від find чекають масив, а він повертає один елемент або undefined.
  • sort() без функції для чисел — і 10 опиняється перед 9.
  • Результат forEach кладуть у змінну — а там undefined. Для нового масиву є map.

Підсумок

  • Індекси з нуля, останній — length - 1 або at(-1).
  • push, splice, sort мутують. Spread, filter, map, slice повертають новий масив.
  • filter — відібрати, map — перетворити, find — знайти один. Зʼєднуються в ланцюжок.
  • reduce згортає масив в одне значення. sort для чисел потребує (a, b) => a - b.