Array.prototype.map()
Метод map()
(відобразити) створює новий масив, наповнений результатами виклику переданої функції на кожному з елементів початкового масиву.
Спробуйте його в дії
Синтаксис
map(callbackFn)
map(callbackFn, thisArg)
Параметри
callbackFn
Функція для виклику на кожному елементі масиву. Її повернене значення додається окремим елементом у новий масив. Ця функція викликається із наступними аргументами:
element
Поточний елемент масиву, який зараз опрацьовується.
index
Порядковий номер поточного елемента масиву, який зараз обробляється.
array
Масив, на якому було викликано метод
map()
.
thisArg
Необов'язковеЗначення для використання як
this
при виконанніcallbackFn
. Більше про це в ітеративних методах.
Результат
Новий масив, куди входять всі результати викликання переданої функції зворотного виклику.
Опис
Метод map()
є ітеративним методом. Він викликає передану функцію callbackFn
для кожного елемента масиву й формує новий масив з отриманих результатів.
callbackFn
закликається лише для тих індексів масиву, що мають присвоєні значення. Вона не закликається для порожніх комірок у розріджених масивах.
Метод map()
є копіювальним методом. Він не вносить змін до this
. Проте функція, передана як callbackFn
, може змінювати масив. Хоча зверніть увагу, що довжина масиву запам'ятовується до моменту першого заклику callbackFn
. Таким чином:
callbackFn
не оброблятиме жодних елементів, доданих поза початковою довжиною масиву, відколи почався викликmap()
.- Зміни за вже обробленими індексами не призводять до повторного заклику на них
callbackFn
. - Якщо наявний, іще не оброблений елемент масиву вже був змінений
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 }
// ]
Перебирання масиву чисел із застосуванням функції з аргументом
Наступний код показує, як map
працює з функцією, яка приймає лише один аргумент. Аргументу буде автоматично присвоєно значення кожного з елементів масиву, поки map
проходить в циклі крізь початковий масив.
const numbers = [1, 4, 9];
const doubles = numbers.map((num) => num * 2);
console.log(doubles); // [2, 8, 18]
console.log(numbers); // [1, 4, 9]
Виклик map() на об'єктах-немасивах
Метод map()
зчитує з this
властивість length
, а потім зчитує кожну цілочислову властивість.
const arrayLike = {
length: 3,
0: 2,
1: 3,
2: 4,
};
console.log(Array.prototype.map.call(arrayLike, (x) => x ** 2));
// [ 4, 9, 16 ]
Узагальнене застосування map() на NodeList
Цей приклад показує, як обходити колекцію об'єктів, зібраних функцією querySelectorAll
. Річ у тім, що querySelectorAll
повертає так званий NodeList
(який є колекцією об'єктів, а не масивом).
В цьому випадку будуть отримані значення всіх на екрані вибраних елементів option
:
const elems = document.querySelectorAll("select option:checked");
const values = Array.prototype.map.call(elems, ({ value }) => value);
Простіший спосіб — застосувати метод Array.from()
.
Використання map() на розріджених масивах
Розріджений масив залишається розрідженим і після map()
. Індекси порожніх комірок будуть порожніми й в поверненому масиві, і функція зворотного виклику не буде на них викликатися.
console.log(
[1, , 3].map((x, index) => {
console.log(`Відвідини ${index}`);
return x * 2;
})
);
// Відвідини 0
// Відвідини 2
// [2, empty, 6]
Застосування parseInt() з map()
(Натхненний цим дописом (англ.))
Поширеним є застосування функцій зворотного виклику з одним аргументом (елементом, який безпосередньо опрацьовується). Деякі функції також зазвичай застосовуються з одним аргументом, навіть якщо вони також приймають додаткові необов'язкові аргументи. Такі звички можуть призводити до заплутаної поведінки.
Припустимо, у нас є:
["1", "2", "3"].map(parseInt);
Хоч можна було б очікувати чогось подібного до [1, 2, 3]
, фактичним результатом є [1, NaN, NaN]
.
parseInt
часто використовується з одним аргументом, хоча приймає два. Перший із них - це вираз, а другий — основа для системи числення.
Array.prototype.map
передає до функції зворотного виклику три аргументи:
- сам елемент
- його порядковий номер
- масив
Третій аргумент ігнорується функцією parseInt
, проте не другий! Ось і джерело можливої плутанини.
Короткий опис кроків циклу:
// parseInt(string, radix) -> map(parseInt(value, index))
/* перша ітерація (index – 0): */ parseInt("1", 0); // 1
/* друга ітерація (index – 1): */ parseInt("2", 1); // NaN
/* третя ітерація (index – 2): */ parseInt("3", 2); // NaN
Як розв'язати цю проблему:
const returnInt = (element) => parseInt(element, 10);
["1", "2", "3"].map(returnInt); // [1, 2, 3]
// В результаті маємо масив чисел (як і очікувалось)
// Те саме, що і вище, але у лаконічному записі через стрілкову функцію
["1", "2", "3"]
.map((str) => parseInt(str)) // [1, 2, 3]
[
// Простіший спосіб і досягнути того ж, і уникнути підводного каменю:
("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)); // [1, 2, 3]
Інакший варіант результату виклику методу map
із функцією parseInt
як аргументом має такий вигляд:
const strings = ["10", "10", "10"];
const numbers = strings.map(parseInt);
console.log(numbers);
// [10, NaN, 2] в результаті може бути несподіваним, з урахуванням опису вище.
Результівний масив містить undefined
Коли повернене значення відсутнє або дорівнює undefined
, маємо:
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]
Специфікації
Сумісність із браузерами
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 |