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
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox for Android Opera Android Safari on iOS Samsung Internet Deno Node.js
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

Дивіться також