Array.prototype.map()
Метод map()
(відобразити) примірників Array
створює новий масив, наповнений результатами виклику переданої функції на кожному з елементів початкового масиву.
Спробуйте його в дії
Синтаксис
map(callbackFn)
map(callbackFn, thisArg)
Параметри
callbackFn
Функція для виклику на кожному елементі масиву. Її повернене значення додається окремим елементом у новий масив. Ця функція викликається із наступними аргументами:
element
Поточний елемент масиву, який зараз опрацьовується.
index
Порядковий номер поточного елемента масиву, який зараз обробляється.
array
Масив, на якому було викликано метод
map()
.
thisArg
Необов'язковеЗначення для використання як
this
при виконанніcallbackFn
. Більше про це в ітеративних методах.
Результат
Новий масив, куди входять всі результати викликання переданої функції зворотного виклику.
Опис
Метод map()
є ітеративним методом. Він викликає передану функцію callbackFn
для кожного елемента масиву й формує новий масив з отриманих результатів. Більше про те, як загалом працюють такі методи, читайте в розділі ітеративних методів.
callbackFn
закликається лише для тих індексів масиву, що мають присвоєні значення. Вона не закликається для порожніх комірок у розріджених масивах.
Метод map()
є узагальненим. Він очікує лишень що значення this
матиме властивість length
, а також властивості з цілочисловими ключами.
Оскільки map
створює новий масив, то викликати його без використання поверненого масиву – антипатерн; натомість слід використовувати forEach
або for...of
.
Приклади
Перетворення масиву чисел на масив їх квадратних коренів
Наступний код приймає масив чисел і створює новий масив, який містить квадратні корені чисел із першого масиву.
const numbers = [1, 4, 9];
const roots = numbers.map((num) => Math.sqrt(num));
// roots тепер [1, 2, 3]
// numbers залишається [1, 4, 9]
Застосування map
для зміни формату об'єктів у масиві
Наступний код приймає масив об'єктів і створює новий масив, який містить нові об'єкти у зміненому форматі.
const kvArray = [
{ key: 1, value: 10 },
{ key: 2, value: 20 },
{ key: 3, value: 30 },
];
const reformattedArray = kvArray.map(({ key, value }) => ({ [key]: value }));
console.log(reformattedArray); // [{ 1: 10 }, { 2: 20 }, { 3: 30 }]
console.log(kvArray);
// [
// { key: 1, value: 10 },
// { key: 2, value: 20 },
// { key: 3, value: 30 }
// ]
Використання parseInt() з map()
Зазвичай використовують функцію зворотного виклику з одним аргументом (елементом, що обробляється). Частина функцій також здебільшого використовується з одним аргументом, попри те, що вони приймають додаткові необов'язкові аргументи. Така звичка може призводити до незрозумілих результатів. Розгляньмо:
["1", "2", "3"].map(parseInt);
Попри те, що можна було б очікувати [1, 2, 3]
, фактичний результат – [1, NaN, NaN]
.
Функція parseInt
нерідко використовується з одним аргументом, але приймає два. Перший – це вираз, а другий – основа числення. Array.prototype.map
передає три аргументи: елемент, індекс і масив. Третій аргумент ігнорується parseInt
, але не другий! Це і є джерелом можливих непорозумінь.
Ось стислий приклад кроків ітерації:
/* перша ітерація (index is 0): */ parseInt("1", 0); // 1
/* друга ітерація (index is 1): */ parseInt("2", 1); // NaN
/* третя ітерація (index is 2): */ parseInt("3", 2); // NaN
Щоб розв'язати цю проблему, визначається інша функція, що приймає лише один аргумент:
["1", "2", "3"].map((str) => parseInt(str, 10)); // [1, 2, 3]
Також можна скористатися функцією Number
, яка приймає лише один аргумент:
["1", "2", "3"].map(Number); // [1, 2, 3]
// Проте, на відміну від parseInt(), Number() також повертає дробові числа та (розібраний) експоненційний запис:
["1.1", "2.2e2", "3e300"].map(Number); // [1.1, 220, 3e+300]
// Для порівняння, якщо використати parseInt() на масиві вище:
["1.1", "2.2e2", "3e300"].map((str) => parseInt(str, 10)); // [1, 2, 3]
Дивіться більше думок на тему в Загрозі необов'язкових аргументів у JavaScript Аллена Вірфса-Брока.
Відображений масив містить undefined
Коли повертається undefined
або нічого, то відображений масив містить undefined
. Якщо замість цього потрібно видалити елемент, в ланцюжок необхідно додати метод filter()
, або використати метод flatMap()
й повернути порожній масив, щоб позначити видалення.
const numbers = [1, 2, 3, 4];
const filteredNumbers = numbers.map((num, index) => {
if (index < 3) {
return num;
}
});
// index починається від 0, тож filterNumbers – це 1,2,3 та undefined.
// filteredNumbers – це [1, 2, 3, undefined]
// numbers – й далі [1, 2, 3, 4]
Відображення з побічними ефектами
Функція зворотного виклику може мати побічні ефекти.
const cart = [5, 15, 25];
let total = 0;
const withTax = cart.map((cost) => {
total += cost;
return cost * 1.2;
});
console.log(withTax); // [6, 18, 30]
console.log(total); // 45
Це не рекомендовано, адже копіювальні методи найкраще використовувати вкупі з чистими функціями. У цьому випадку – можемо захотіти пройтися масивом двічі.
const cart = [5, 15, 25];
const total = cart.reduce((acc, cost) => acc + cost, 0);
const withTax = cart.map((cost) => cost * 1.2);
Іноді цей патерн доходить до крайнощів, і єдиною корисною річчю, котру робить map()
, виявляються побічні ефекти.
const products = [
{ name: "спортивна автівка" },
{ name: "ноутбук" },
{ name: "телефон" },
];
products.map((product) => {
product.price = 100;
});
Як згадувалось вище, це є антипатерном. Якщо повернене значення map()
не використовується, краще натомість використати forEach()
або цикл for...of
.
products.forEach((product) => {
product.price = 100;
});
Або, якщо необхідно створити новий масив:
const productsWithPrice = products.map((product) => {
return { ...product, price: 100 };
});
Використання третього аргументу callbackFn
Аргумент array
корисний тоді, коли є потреба звернутися до іншого елемента масиву, особливо коли немає змінної, що посилається на цей масив. У наступному прикладі спочатку використовується filter()
для видобування додатних значень, а потім map()
– для створення нового масиву, в якому кожний елемент є середнім арифметичним своїх сусідів і самого себе.
const numbers = [3, -1, 1, 4, 1, 5, 9, 2, 6];
const averaged = numbers
.filter((num) => num > 0)
.map((num, idx, arr) => {
// Без аргументу arr немає способу легко отримати доступ до
// проміжного масиву без збереження його в змінній.
const prev = arr[idx - 1];
const next = arr[idx + 1];
let count = 1;
let total = num;
if (prev !== undefined) {
count++;
total += prev;
}
if (next !== undefined) {
count++;
total += next;
}
const average = total / count;
// Зберегти два дробові розряди
return Math.round(average * 100) / 100;
});
console.log(averaged); // [2, 2.67, 2, 3.33, 5, 5.33, 5.67, 4]
Аргумент array
– це не масив, що створюється – немає способу звернутися до масиву, що створюється, з функції зворотного виклику.
Використання map() на розріджених масивах
Розріджений масив залишається розрідженим і після map()
. Індекси порожніх комірок будуть порожніми й в поверненому масиві, і функція зворотного виклику не буде на них викликатися.
console.log(
[1, , 3].map((x, index) => {
console.log(`Відвідини ${index}`);
return x * 2;
}),
);
// Відвідини 0
// Відвідини 2
// [2, empty, 6]
Виклик map() на об'єктах-немасивах
Метод map()
зчитує властивість length
з this
, а потім звертається до кожної властивості, чий ключ – невід'ємне ціле число, менше за length
.
const arrayLike = {
length: 3,
0: 2,
1: 3,
2: 4,
3: 5, // ігнорується map(), оскільки length – 3
};
console.log(Array.prototype.map.call(arrayLike, (x) => x ** 2));
// [ 4, 9, 16 ]
Цей приклад демонструє, як ітерувати по колекції об'єктів, зібраній за допомогою querySelectorAll
. Такий підхід пов'язаний з тим, що querySelectorAll
повертає NodeList
(колекцію об'єктів). У цьому випадку повертаються значення всіх обраних значень option
на екрані:
const elems = document.querySelectorAll("select option:checked");
const values = Array.prototype.map.call(elems, ({ value }) => value);
Також для перетворення elems
на масив можна скористатися Array.from
, а потім викликати метод map()
.
Специфікації
Сумісність із браузерами
desktop | mobile | server | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
map
|
Chrome Full support 1 | Edge Full support 12 | Firefox Full support 1.5 | Internet Explorer Full support 9 | Opera Full support 9.5 | Safari Full support 3 | WebView Android Full support 37 | Chrome Android Full support 18 | Firefox for Android Full support 4 | Opera Android Full support 10.1 | Safari on iOS Full support 1 | Samsung Internet Full support 1.0 | Deno Full support 1.0 | Node.js Full support 0.10.0 |