Middle: питання на співбесіді з теми «Масиви й об'єкти»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
4 питання
Обидва зберігають пари «ключ - значення», але Map створений саме як словник, а об'єкт - як запис з полями.
Відмінності:
- Ключі. У об'єкта ключі - лише рядки й символи (число
1стане рядком'1'). УMapключем може бути будь-що: об'єкт, функція,NaN. - Розмір.
map.size- одразу; для об'єкта -Object.keys(obj).length. - Порядок.
Mapзберігає порядок вставки. У об'єкта порядок теж визначений, але з винятком: ключі, схожі на цілі числа, йдуть першими за зростанням. - Немає успадкованих ключів. Звичайний об'єкт має прототип, і ключі на кшталт
constructorчи__proto__можуть дати сюрпризи.Mapпорожній по-справжньому. - Швидкодія на частих додаваннях і видаленнях у
Mapзазвичай краща. - JSON.
JSON.stringify(map)дає{}- його треба перетворювати:Object.fromEntries(map).
const visits = new Map();
const button = document.querySelector('#buy');
visits.set(button, 1); // ключ - DOM-елемент
visits.set(button, visits.get(button) + 1);
visits.has(button); // true
for (const [element, count] of visits) { /* ... */ }
Коли що: об'єкт - для структур з відомими полями ({ id, name, email }), JSON і конфігурацій. Map - для словників з динамічними ключами, частих змін і ключів-не-рядків. Для набору унікальних значень - Set.
Змінюють масив на місці (мутують): push, pop, shift, unshift, splice, sort, reverse, fill, copyWithin.
Повертають новий, не чіпаючи початковий: map, filter, slice, concat, flat, flatMap, а також нові (ES2023) toSorted, toReversed, toSpliced і with.
const scores = [30, 10, 20];
const sorted = scores.sort((a, b) => a - b);
// scores теж став [10, 20, 30]: sort змінює масив і повертає його ж
const safe = [30, 10, 20];
const sortedCopy = safe.toSorted((a, b) => a - b); // safe лишився [30, 10, 20]
const replaced = safe.with(0, 99); // [99, 10, 20], safe не змінився
Чому це важливо:
- У React, Vue, Redux мутація стану на місці ламає виявлення змін: посилання на масив те саме, і компонент може не оновитися. Тому
toSorted()чи[...items].sort()замістьitems.sort(). - Масив з аргументу функції належить коду, який його передав. Відсортувати його на місці - змінити чужі дані непомітно.
Ще пастка sort: без функції порівняння елементи порівнюються як рядки, тож [10, 9, 1].sort() дає [1, 10, 9]. Для чисел потрібен (a, b) => a - b, для українського тексту - (a, b) => a.localeCompare(b, 'uk').
Групування масиву за ознакою довго писали вручну через reduce. З ES2024 для цього є вбудовані функції.
Object.groupBy(items, callback) - результат у звичайному об'єкті, ключі - те, що повернув колбек:
const orders = [
{ id: 1, status: 'paid', total: 100 },
{ id: 2, status: 'new', total: 50 },
{ id: 3, status: 'paid', total: 70 },
];
const byStatus = Object.groupBy(orders, (order) => order.status);
// { paid: [{ id: 1, ... }, { id: 3, ... }], new: [{ id: 2, ... }] }
const bySize = Object.groupBy(orders, ({ total }) => (total >= 70 ? 'large' : 'small'));
Map.groupBy(items, callback) - результат у Map. Потрібен, коли ключ - не рядок: об'єкт, дата, число, яке не має перетворюватися на рядок:
const byCustomer = Map.groupBy(orders, (order) => customersById.get(order.customerId));
byCustomer.get(someCustomer); // замовлення конкретного об'єкта-клієнта
Особливості:
- це статичні функції, а не методи масиву:
Object.groupBy(arr, fn), а неarr.groupBy(fn). Метод масиву планували, але він конфліктував зі старими бібліотеками, що розширювалиArray.prototype; - об'єкт від
Object.groupByмає прототипnull: у нього немаєhasOwnProperty,toStringтощо.result.hasOwnProperty('paid')кине помилку - перевіряйте черезObject.hasOwn(result, 'paid')чи'paid' in result; - групи відсутніх значень просто не створюються - порожніх масивів для «ненайдених» статусів не буде;
- елементи в групах ідуть у порядку вихідного масиву.
Як це робили раніше (і досі доводиться в старих оточеннях):
const byStatus = orders.reduce((groups, order) => {
(groups[order.status] ??= []).push(order);
return groups;
}, {});
Порівняння з Laravel: це аналог collect($orders)->groupBy('status'). Агрегати по групах далі рахують звичайними методами: Object.entries(byStatus).map(([status, list]) => [status, list.length]).
Підтримка: усі сучасні браузери з 2024 року і Node.js 21+. Для старших середовищ - поліфіл (core-js) чи reduce.
Set - колекція унікальних значень. Повторне додавання того самого значення нічого не змінює.
const tags = new Set(['php', 'laravel', 'php']);
tags.size; // 2
tags.add('vue');
tags.has('laravel'); // true, за O(1)
tags.delete('php');
[...tags]; // ['laravel', 'vue'] - порядок додавання зберігається
Найчастіший прийом - прибрати дублікати:
const unique = [...new Set(ids)];
Унікальність визначається як === (з винятком: NaN дорівнює NaN). Тому об'єкти порівнюються за посиланням - два різні об'єкти { id: 1 } обидва потраплять у множину. Для унікальності об'єктів за полем використовують Map з ключем-полем.
Методи множин (ES2025) - нарешті вбудовані операції теорії множин. Кожен повертає новий Set:
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
a.union(b); // {1, 2, 3, 4}
a.intersection(b); // {2, 3}
a.difference(b); // {1} - є в a, немає в b
a.symmetricDifference(b); // {1, 4} - є лише в одній з множин
new Set([2]).isSubsetOf(a); // true
a.isSupersetOf(new Set([1])); // true
a.isDisjointFrom(new Set([9])); // true - спільних елементів немає
Практичні застосування:
- синхронізація зв'язків: які теги додати й які прибрати при збереженні форми:
const toAttach = selected.difference(current);
const toDetach = current.difference(selected);
Це те, що в Laravel робить sync() для зв'язків «багато-до-багатьох».
- права:
required.isSubsetOf(userPermissions)- чи має користувач усі потрібні права; - швидкі перевірки належності у фільтрах замість
array.includesу циклі.
Аргументом нових методів може бути не лише Set, а будь-який об'єкт із size, has() і keys() - наприклад, Map (порівнюються ключі). Масив напряму не підходить: a.union([4]) кине помилку, потрібно a.union(new Set([4])).
Підтримка: усі сучасні браузери з 2024 року, Node.js 22+.