Array.prototype.map()

Метод map() створює новий масив з результатами виклику наданої функції на кожному елементі масиву, який викликав метод.

Синтаксис

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
    // Повернути елемент нового масиву new_array
}[, thisArg])

Параметри

callback
Функція, яка повертає елемент нового масиву. Приймає три аргументи:
currentValue
Поточний елемент масиву.
indexOptional
Індекс поточного елементу масиву
arrayOptional
Сам масив, на якому був викликаний map.
thisArgOptional
Значення, що буде використане як this при виконанні callback.

Значення, що повертається (return value)

Новий масив, кожен елемент якого є результатом функції callback.

Опис

Метод map викликає передану callback-функцію один раз для кожного елементу масиву, в заданому порядку, та створює новий масив з результатів. callback викликаться тільки для індексів яким відповідають значення, включно з undefined. Функція не викликається для елеметів значення яких відсутні (мається на увазі, індекси які не були явно задані, які були видалені або яким не було присвоєне значення).

Остільки map створює новий масив, викликати його, якщо ви не збираєтесь використовувати повернений масив, є антишаблоном; скористайтесь натомість forEach або for-of. Ознаки того, що вам не підходить метод map: А) Ви не використовуєте масив, який він повертає, і/або Б) Ви не повертаєте значення у функції callback.

callback викликається з трьома аргументами: значення елемента, індекс елемента, та масив на якому операцію було викликано.

Якщо thisArg параметр переданий в map, він буде використовуватись як ключове слово this  для callback-функції. В іншому випадку, значення undefined буде використане як this. Значення this, яке спостерігається в callback-функції, в кінцевому рахунку визначається згідно звичайних правил для визначення this, видимого з функції.

map не змінює масив, на якому був викликаний (хоча callback, якщо був викликаний, може змінити).

Діапазон елементів, які обробляє метод map, визначений до того як callback-функція буде визвана вперше. Елементи які будуть додані до масиву після виклику map, не будуть оброблені callback-функцією. Якщо існуючі в масиві елементи змінені або видалені, їхні значення при потраплянні в callback-функцію, будуть такими якими вони є на той час коли map обробляє їх. Елементи які були видалені до і після того як map був визваний, ігноруються. 

Згідно з алгоритмом, визначеним у специфікації, якщо масив на якому було викликано map, розріджений, то масив на виході теж буде розрідженим, залишаючи ті ж самі індекси пустими.

Приклади

Перетворення масиву з числами в масив квадратних коренів

Наступний код бере масив з числами і створює новий масив, який складається з квадратних коренів чисел з першого масиву.

var numbers = [1, 4, 9];
var roots = numbers.map(function(num) {
return Math.sqrt(num)
});
// roots тепер [1, 2, 3]
// numbers залишається [1, 4, 9]

Використання map для переформатування об'єктів в масиві

Наступний код використовує масив з об'єктами щоб створити новий масив з переформатованими об'єктами.

var kvArray = [{key:1, value:10}, 
               {key:2, value:20}, 
               {key:3, value: 30}];

var reformattedArray = kvArray.map(obj =>{ 
   var rObj = {};
   rObj[obj.key] = obj.value;
   return rObj;
});
// reformattedArray тепер [{1:10}, {2:20}, {3:30}], 

// kvArray залишається:
// [{key:1, value:10}, 
//  {key:2, value:20}, 
//  {key:3, value: 30}]

Перетворення масиву чисел використовуючи функцію з аргументом

Наступний код показує як map працює коли функція що потребує один аргумент, використовує його.  Аргумент буде автоматично присвоєний з кожного елементу масиву коли map буде проходитись по оригінальному масиву..

var numbers = [1, 4, 9];
var doubles = numbers.map(function(num) {
  return num * 2;
});

// doubles тепер [2, 8, 18]
// numbers залишається [1, 4, 9]

Загальне використання map

Цей приклад показує як використовувати map на рядках (String) щоб отримати масив байтів в ASCII кодуванні яке відображає значення літер:

var map = Array.prototype.map;
var a = map.call('Hello World', function(x) { 
   return x.charCodeAt(0);
});
// a тепер дорівнює [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]

Загальне використання map з querySelectorAll

Даний приклад показує, як виконати перебір набору об'єктів, отриманих методом querySelectorAll. Тому що метод querySelectorAll повертає NodeList, який є колекцією об'єктів.

У даному випадку ми повертаємо значення всіх вибраних опцій на екрані:

var elems = document.querySelectorAll('select option:checked');
var values = Array.prototype.map.call(elems, function(obj) {
  return obj.value;
});

Це простіше зробити методом Array.from().

Хитрий спосіб використання

(натхненний цим блог-постом)

Загальноприйнято використовувати функцію callback з одним аргументом (елемент по якому проходиться функція). Деякі функції також використовуються з одним аргументом, хоча можуть приймати і більше додаткових аргументів, що не є обов'язковими. Це може призводити до неочікуваної поведінки.

Розглянемо:

["1", "2", "3"].map(parseInt);

Хоча можна було б очікувати [1, 2, 3], справжнім результатом буде [1, NaN, NaN].

Метод parseInt часто використовується з одним аргументом, але приймає два. Перший - це вираз, а другий - основа системи числення для функції зворотного виклику. Array.prototype.map передає 3 аргументи:

  • елемент
  • індекс
  • масив

Третій аргумент ігнорується методом parseInt, але не другий, звідси й можлива плутанина. Ось стислий огляд кроків перебору:

// parseInt(string, radix) -> map(parseInt(value, index))
/*  first iteration (index is 0): */ parseInt("1", 0); // 1
/* second iteration (index is 1): */ parseInt("2", 1); // NaN
/*  third iteration (index is 2): */ parseInt("3", 2); // NaN

Поговоримо про рішення.

function returnInt(element) {
  return parseInt(element, 10);
}

['1', '2', '3'].map(returnInt); // [1, 2, 3]
// Результатом є масив чисел (як і очікувалось)

// Те саме, але з використанням лаконічного стрілкового синтаксису
['1', '2', '3'].map( str => parseInt(str) );

// Простіший спосіб досягти вищевказаного
['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 в якості параметра, виглядає наступним чином:

var xs = ['10', '10', '10'];

xs = xs.map(parseInt);

console.log(xs);
// Результат 10,NaN,2 може бути неочікуваним, з огляду вищеописане.

Масив містить значення undefined

Коли повертається undefined або нічого не повертається:

var numbers = [1, 2, 3, 4];
var filteredNumbers = numbers.map(function(num, index) {
  if(index < 2) {
     return num;
  }
});
// filteredNumbers дорівнює [1, 2, undefined, undefined]
// numbers досі дорівнює [1, 2, 3, 4]

Поліфіл

Метод map був доданий до ECMA-262 стандарту в 5-тій редакції; тому він може бути присутнім не у всіх реалізаціях стандарту. Ви можете обійти це, вставляючи наступний код на початок вашого скритпу, дозволяючи використовувати map в реалізаціях які ще його не підтримують. Цей алгоритм є точно таким який вказаний в  ECMA-262, 5му виданні, передбачаючи що Object, TypeError, і Array мають свої власні значення  і що callback.call обчислює початкове значення Function.prototype.call.

// Функціональні кроки ECMA-262, версія 5, 15.4.4.19
// Довідка: http://es5.github.io/#x15.4.4.19
if (!Array.prototype.map) {

  Array.prototype.map = function(callback, thisArg) {

    var T, A, k;

    if (this == null) {
      throw new TypeError(' this is null or not defined');
    }

    // 1. Нехай O дорівнює результату виклику ToObject з |this| 
    //    в якості аргументу.
    var O = Object(this);

    // 2. Нехай lenValue дорівнює результату виклику внутрішнього методу O
    //    Get з аргументом "length".
    // 3. Нехай len дорівнює ToUint32(lenValue).
    var len = O.length >>> 0;

    // 4. Якщо IsCallable(callback) дорівнює false, викинути виняток TypeError.
    // Див.: http://es5.github.com/#x9.11
    if (typeof callback !== 'function') {
      throw new TypeError(callback + ' is not a function');
    }

    // 5. Якщо надано thisArg, нехай T дорівнює thisArg; інакше нехай T дорівнює undefined.
    if (arguments.length > 1) {
      T = thisArg;
    }

    // 6. Нехай A дорівнює новому масиву, створеному виразом new Array(len), 
    //    де Array - це стандартний вбудований конструктор з таким ім'ям, 
    //    а len дорівнює значенню len.
    A = new Array(len);

    // 7. Нехай k дорівнює 0
    k = 0;

    // 8. Повторювати, доки k < len
    while (k < len) {

      var kValue, mappedValue;

      // а. Нехай Pk дорівнює ToString(k).
      //   Цей метод неявно застосовується до лівого операнда оператора in
      // б. Нехай kPresent дорівнює результату виклику внутрішнього методу O
      //    HasProperty з аргументом Pk.
      //   Цей крок можна об'єднати з в
      // в. Якщо kPresent дорівнює true, тоді
      if (k in O) {

        // і. Нехай kValue дорівнює результату виклику внутрішнього методу O
        //    Get з аргументом Pk.
        kValue = O[k];

        // ii. Нехай mappedValue дорівнює результату виклику внутрішнього
        //     методу callback Call з T у якості значення this та списком 
        //     аргументів, що містить kValue, k та O.
        mappedValue = callback.call(T, kValue, k, O);

        // iii. Викликати внутрішній метод A DefineOwnProperty з аргументами
        // Pk, Property Descriptor
        // { Value: mappedValue,
        //   Writable: true,
        //   Enumerable: true,
        //   Configurable: true },
        // та false.

        // У переглядачах, що підтримують Object.defineProperty, використовуйте:
        // Object.defineProperty(A, k, {
        //   value: mappedValue,
        //   writable: true,
        //   enumerable: true,
        //   configurable: true
        // });

        // Для найкращої підтримки переглядачів, використовуйте:
        A[k] = mappedValue;
      }
      // г. Збільшити k на 1.
      k++;
    }

    // 9. повернути A
    return A;
  };
}

Специфікації

Специфікація Статус Коментар
ECMAScript 5.1 (ECMA-262)
The definition of 'Array.prototype.map' in that specification.
Standard

Початкове визначення. Реалізоване в JavaScript 1.6.

ECMAScript 2015 (6th Edition, ECMA-262)
The definition of 'Array.prototype.map' in that specification.
Standard
ECMAScript Latest Draft (ECMA-262)
The definition of 'Array.prototype.map' in that specification.
Draft

Сумісність з веб-переглядачами

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidFirefox for AndroidOpera for AndroidSafari on iOSSamsung InternetNode.js
mapChrome Full support 1Edge Full support 12Firefox Full support 1.5IE Full support 9Opera Full support YesSafari Full support YesWebView Android Full support ≤37Chrome Android Full support 18Firefox Android Full support 4Opera Android Full support YesSafari iOS Full support YesSamsung Internet Android Full support 1.0nodejs Full support Yes

Legend

Full support  
Full support

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