Map() в JavaScript: главный секрет метода

Андрій Денисенко

Редакция Highload разобралась, как и зачем использовать метод map() массива JavaScript, на реальных примерах.

Содержание:
1. Что представляет собой метод map() в JavaScript?
2. Синтаксис и параметры метода map() в JavaScript
2.1. Параметры
2.2. Возвращаемое значение
3. Примеры использования map() в JavaScript
3.1. С одним аргументом
3.2. С двумя аргументами
3.3. С тремя аргументами
4. Совместимость
5. Полифил
6. Когда не следует использовать map()
7. Дополнительные примеры
7.1. Преобразование строки в массив
7.2. Рендеринг списков с помощью React
7.3. Переформатирование массива
8. Сложности использования метода map()
Заключение

1. Что представляет собой метод map() в JavaScript?

Метод map() массивов JavaScript применяет указанную функцию последовательно к каждому элементу массива и создает новый массив из результатов.

const args = [1, 2, 3, 4];
 const squares = args.map(x => x * x);
console.log(squares)

Приведенный выше код возводит в квадрат элементы массива args и создает из результатов новый массив squares:

[1, 4, 9, 16]

Метод map() не изменяет исходный массив и выполняет функцию только для тех элементов, которым присвоены значения (в том числе, undefined).

Метод map() — это возможность ECMAScript5 (ES5). ES5 (JavaScript 2009) полностью поддерживается всеми современными браузерами.

2. Синтаксис и параметры метода map() в JavaScript

Для метода map() используются такие сигнатуры:

// Сигнатура со стрелкой
array.map((element) => { /* ... */ })
array.map((element, index) => { /* ... */ })
array.map((element, index, arr) => { /* ... */ })
 
// Функция обратного вызова
array.map(callbackFn)
array.map(callbackFn, thisValue)
 
// Вложенная функция обратного вызова
array.map(function(element) { /* ... */ })
array.map(function(element, index) { /* ... */ })
array.map(function(element, index, arr){ /* ... */ })
array.map(function(element, index, arr) { /* ... */ }, thisValue)

2.1. Параметры

array Исходный массив, к элементам которого будет применена функция.
function() Обязательный.

Функция, которая будет применяться к каждому элементу массива.

element Обязательный.

Значение текущего элемента.

index Необязательный.

Индекс текущего элемента.

arr Необязательный.

Массив текущего элемента.

thisValue Необязательный.

Значение по умолчанию — undefined.

Значение, которое передается функции в качестве this.

2.2. Возвращаемое значение

Массив с результатами применения функции к каждому элементу исходного массива.

3. Примеры использования map() в JavaScript

3.1. С одним аргументом

array.map((element) => { /* ... */ })

Допустим, имеется список из топ-3 игр 2021 года. Требуется вывести названия игр и разработчиков. Для этого подойдут сигнатуры с одним аргументом. Вот как это можно сделать, используя сигнатуру map() со стрелкой:

<!DOCTYPE html>
<html>    
<head>
    <title>
        Метод map() массива JavaScript
    </title>
</head>
 
<body>
    <div id="root"></div>
     
    <script>
        var el = document.getElementById('root');
       
        const top3_games = [
            {name: "Deathloop", dev: "Arkane Studios"},
            {name: "Halo Infinite", dev: "343 Industries"},
            {name: "Metroid Dread", dev: "MercurySteam and Nintendo EPD"}
        ];
 
        var game_list = top3_games.map(game => [game.name, game.dev].join(" by "));
         
        el.innerHTML = JSON.stringify(game_list);
    </script>
</body>
 
</html>

В результате выполнения в элемент div с идентификатором root будет помещено следующее:

["Deathloop by Arkane Studios","Halo Infinite by 343 Industries","Metroid Dread by MercurySteam and Nintendo EPD"]

Тот же самый результат получим с использованием функции обратного вызова:

var game_list = top3_games.map(getInfo);
         
      function getInfo(game) {
            return [game.name, game.dev].join(" by ");
      }

и с помощью вложенной функции:

var game_list = top3_games.map(function(game) {
            return [game.name, game.dev].join(" by ");
      });

Запись со стрелкой — самая компактная, в то время как в отдельной функции можно гибко разместить крупный фрагмент кода.

3.2. С двумя аргументами

Теперь давайте выведем игры под порядковыми номерами. Для получения порядкового номера воспользуемся вторым аргументом функции: index.

Для простоты возьмем пример сигнатуры со стрелкой и изменим строку, в которой применяется метод map():

var game_list = top3_games.map((game, index) => `${index + 1}. ${[game.name, game.dev].join(" by ")}`);

Теперь в начале каждого значения будет указан его порядковый номер, который на единицу больше индекса:

["1. Deathloop by Arkane Studios","2. Halo Infinite by 343 Industries","3. Metroid Dread by MercurySteam and Nintendo EPD"]

3.3. С тремя аргументами

В качестве третьего аргумента функции обратного вызова передается исходный массив. Мы можем использовать его, например, если нужно указать общее количество элементов:

var game_list = top3_games.map((game, index, arr) => `${index + 1} of ${arr.length}: ${[game.name, game.dev].join(" by ")}`);

В результате получим:

["1 of 3: Deathloop by Arkane Studios","2 of 3: Halo Infinite by 343 Industries","3 of 3: Metroid Dread by MercurySteam and Nintendo EPD"]

4. Совместимость

Метод map() JavaScript поддерживается современными браузерами. В приведенной ниже таблице указаны версии браузеров и платформ, начиная с которых реализована его поддержка.

Браузеры

Chrome 1
Edge 12
Firefox 1.5
IE 9
Opera 9.5
Safari 3

Мобильные браузеры

WebView Android 37
Chrome Android 18
Firefox for Android 4
Opera Android 10.1
Safari on iOS 1
Samsung Internet 1.0

Платформы

Deno 1.0
Node.js 0.10.0

5. Полифил

Что делать, если используемая версия браузера не поддерживает метод map()? В этом случае метод можно заменить полифилом. Полифил (англ. polyfill) — это фрагмент кода, реализующий новую функциональность в устаревших версиях браузеров, которые не поддерживают современные функции.

Полифил для Array.prototype.map доступен в core-js. Но его можно написать и самостоятельно. Для этого нужно реализовать функцию, в которой:

  1. создается новый массив;
  2. в цикле делается проход по всем элементам исходного массива;
  3. для каждого из них вызывается указанная функция;
  4. результат функции добавляется в новый массив;
  5. после завершения цикла новый массив возвращается из функции.
<!DOCTYPE html>
<html>    
<head>
    <title>
        Полифил для метода map() массива JavaScript
    </title>
</head>
 
<body>
    <div id="root"></div>
     
    <script>
        var el = document.getElementById('root');
       
        Array.prototype.pfMap = function(callbackFn) {
            var result = [];              
            for (var i = 0; i < this.length; i++) {
                result.push(callbackFn(this[i], i, this));
            }
            return result;
        }
 
        var doubled = [1, 2, 3, 4, 5].pfMap(x => x * 2);
         
        el.innerHTML = JSON.stringify(doubled);
    </script>
</body>
 
</html>  

В этом примере кода на странице выводится массив с удвоенными значениями элементов исходного массива:

[2,4,6,8,10]

6. Когда не следует использовать map()

map() — один из методов, с помощью которых можно производить итерацию по массиву. Но существуют ситуации, когда использовать его нецелесообразно. Это случаи, когда:

  1. когда возвращаемый массив не используется;
  2. когда функция обратного вызова не возвращает значения.

В таких случаях рекомендуется пользоваться forEach и for...of.

7. Дополнительные примеры

7.1. Преобразование строки в массив

Как уже упоминалось в разделе выше о полифилах, метод map() — это метод прототипа массива Array.prototype.map. Возможности JavaScript позволяют перенести этот метод в контекст строки и выполнить итерацию, перебрав ее по символам. Это можно сделать, вызвав map() с помощью метода call().

<!DOCTYPE html>
<html>    
<head>
    <title>
        Преобразование строки в массив
    </title>
</head>
 
<body>
    <div id="root"></div>
     
    <script>
        var el = document.getElementById('root');
       
        const line = "Do or do not. There is no try.";
        const map = Array.prototype.map;
 
        const newLine = map.call(line, currentChar => {
            return `${currentChar}`
        });
 
        el.innerHTML = JSON.stringify(newLine);
    </script>
</body>
</html>  

В итоге каждый символ станет отдельным строковым элементом нового массива:

["D","o"," ","o","r"," ","d","o"," ","n","o","t","."," ","T","h","e","r","e"," ","i","s"," ","n","o"," ","t","r","y","."]

7.2. Рендеринг списков с помощью React

Метод map() можно использовать для рендеринга списков, например в React. Вот содержимое JSX-файла компонента, который выводит слова из массива в виде списка:

import React from "react";
import ReactDOM from "react-dom";
 
const words = ["каждый", "охотник", "желает", "знать", "где", "сидит", "фазан"];
 
const WordList = () => (
  <div>
    <ul>{words.map(word => <li key={word}> {word} </li>)}</ul>
  </div>
);
 
ReactDOM.render(<WordList />, document.getElementById("root"));

Результат:

  • каждый
  • охотник
  • желает
  • знать
  • где
  • сидит
  • фазан

7.3. Переформатирование массива

С помощью метода map() можно изменить объекты внутри массива, по которому производится итерация. В приведенном ниже коде проводится преобразование массива, в котором указаны названия топ-3 языков программирования в 2021 году и годы их создания, в массив с названиями этих языков и их местами по популярности.

<!DOCTYPE html>
<html>    
<head>
    <title>
        Переформатирование массива JavaScript с помощью map()
    </title>
</head>
 
<body>
    <div id="root"></div>
     
    <script>
        var el = document.getElementById('root');
       
        const top3_languages = [
            {name: "Python", year: 1991},
            {name: "Java", year: 1995},
            {name: "JavaScript", year: 1995}
        ];
 
        var language_places = top3_languages.map(function(game, index) {
            var item = {};
            item["place"] = index + 1;
            item["name"] = game.name;
            return item;
        });
         
        el.innerHTML = JSON.stringify(language_places);
    </script>
</body>
 
</html>

Код выводит следующий массив:

[{"place":1,"name":"Python"},{"place":2,"name":"Java"},{"place":3,"name":"JavaScript"}]

8. Сложности использования метода map()

Методу map() можно передавать не только пользовательские, но и встроенные функции. Передавая встроенные функции, следует обращать особое внимание на количество аргументов, которые они принимают. Рассмотрим следующий пример.

<!DOCTYPE html>
<html>    
<head>
    <title>
        map() и встроенные функции
    </title>
</head>
 
<body>
    <div id="root"></div>
    <script>
        document.getElementById("root").innerHTML =
          ["5", "4", "3", "2", "1"].map(parseInt);
    </script>
</body>
</html>  

На первый взгляд, этот код тривиален и должен выдать последовательность 5, 4, 3, 2, 1, преобразовав строки в целые числа. Но фактически мы получаем значения 5, NaN, NaN, 2, 1. Почему так происходит?

Метод parseInt принимает два аргумента: строку и основание системы счисления. В то же время функция обратного вызова передает три значения: элемент, индекс и массив.

Посмотрим, что происходит при выполнении этого кода. Из трех значений, передаваемых функции parseInt, последнее игнорируется. В качестве аргумента для преобразования передается 5, а в качестве основания системы счисления — 0 (индекс первого элемента). Когда parseInt принимает второе значение 0, а строка не начинается с «0x», используется десятичная система счисления. Поэтому в результате первое значение — 5.

Диапазон значений для основания системы счисления в parseInt — числа от 2 до 36. Второе значение в массиве («4») имеет индекс 1, который выходит за пределы допустимых значений второго параметра parseInt. Поэтому в результате выполнения итерации для этого значения получаем NaN.

На следующей итерации будет передано основание 2, но в двоичной системе счисления нет цифры 3, и снова получаем NaN.

Далее получаем 2 и 1, потому что в троичной и четверичной системах есть эти цифры.

Существует несколько решений этой проблемы.

Можно создать «враппер» для parseInt, который будет явно принимать один аргумент и передавать основание 10:

arr = ["5", "4", "3", "2", "1"];

       function toInt(el){
           return parseInt(el, 10);
       }
       document.getElementById("root").innerHTML = arr.map(toInt);

То же решение можно переписать с использованием сигнатуры со стрелкой:

document.getElementById("root").innerHTML = arr.map(
           str => parseInt(str)
       );

И третьим решением будет применение Number:

document.getElementById("root").innerHTML = arr.map(Number);

Заключение

Метод map() массива JavaScript пригодится, когда требуется применить определенную функцию к произвольному количеству аргументов и получить результаты в новом массиве. Его целесообразно использовать, когда нужно оставить исходный массив без изменений. В противном случае рекомендуется использовать другие средства, не создающие нового массива.

Как обычно, в заключение предлагаем посмотреть подробное видео про метод массива map():

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023