Тип Поста

Использование методов map() & reduce() в JavaScript

Использование методов map() & reduce() в JavaScript

С приходом в процесс разработки новой версии ECMAScript 6 со всеми её восхитительными возможностями можно легко забыть, что и в старом добром ECMAScript 5 есть на что обратить внимание. А именно на встроенные методы map() и reduce(), которые содержит JavaScript Array объект.

Если вы всё ещё не используете методы map() и reduce(), то самое время начать это делать. Все современные платформы поддерживают ECMAScript 5 по умолчанию. Использование данных методов может сделать ваш код более чистым, читаемым и легко поддерживаемым, что в конечном итоге приведет к более изящной разработке.

Производительность

Стоит заметить, что в вашем коде должен быть соблюден баланс между читаемостью, поддерживаемостью и производительностью, причем именно в том соотношении, которое будет уместно для определенной ситуации. На текущий момент, браузеры показывают большую производительность при использовании традиционных техник, как, например, циклы.

Возможно, в таком случае, даст хороший результат подход написания кода, при котором, в первую очередь, делается упор на читаемость и поддерживаемость, а затем производится его оптимизация, но только лишь в том случае, если в этом есть необходимость. Предварительная оптимизация – это не слишком хорошая вещь.

Также нужно отметить, что использование таких методов, как map() и reduce() сейчас, может иметь огромное преимущество в будущем, в том случае, если браузерные движки будут оптимизированы именно под эти методы.

Использование метода map()

Mapping – это фундаментальная техника функционального программирования для выполнения каких-либо операций над всеми элементами массива, в результате которой формируется новый массив той же размерности, но с изменённым контентом.

Для более лучшего понимания процесса, давайте рассмотрим простой случай использования данной техники. К примеру, представим, что у нас есть массив слов, и нам нужно преобразовать их в массив, который будет содержать длину каждого слова. Само собой, данный пример не является образчиком сложности, с которой можно столкнуться при разработке реального приложения, но понимание того, как работает эта техника на самом простом примере, поможет вам без проблем применить её там, где от неё будет огромная польза.

Вероятно, вы уже знаете, как сделать описанное выше используя, например, цикл for для массива. Один из вариантов цикла может выглядеть так:

var animals = [ "кот", "собака", "рыба" ],
    lengths = [],
    item,
    count,
    loops   = animals.length;

for( count = 0; count < loops; count++ )
{
  item = animals[ count ];
  lengths.push( item.length );
}

console.log( lengths ); // [ 3, 6, 4 ]

Мы определили несколько вспомогательных переменных: массив с именем animals, который содержит все наши слова; пустой массив lengths для конечного результата работы цикла; переменная item, в которой временно будет содержаться каждый элемент цикла, над которым нужно произвести определённую манипуляцию. Для самого цикла мы установили переменные count – номер итерации цикла и loops – размер массива слов. Затем мы производим итерацию цикла по всем элементам массива animals. Для каждого элемента массива производится вычисление количества символов слова, и вставка полученной длины методом push() в результативный массив lengths.

Возможно, мы могли бы сделать это более кратко без использования переменной item, просто вставив длину элемента массива animals[ count ] напрямую в массив lengths без промежуточного назначения. Это помогло бы нам уменьшить количество кода, но также сделало бы весь код менее читаемым даже для такого простого примера. Аналогично, чтобы сделать этот пример более производительным, но менее очевидным, можно было использовать длину массива animals для создания массива lengths с помощью new Array( animals.length ), а затем вставить элементы по индексу, вместо использования метода push(). Выбор способа реализации задачи должен определяться тем, как вы собираетесь использовать написанный код приложения в реальном мире.

Описанный выше способ реализации является полностью работоспособным и должен работать под любым движком JavaScript, выполняя свою работу. Но узнав, как можно сделать тоже самое с помощью метода map(), использование старого способа вам покажется громоздким и неуклюжим.

Позвольте показать вам, как можно решить нашу задачу с помощью метода map():

var animals = [ "кот", "собака", "рыба" ];

var lengths = animals.map( function( animal )
{
  return animal.length;
} );

console.log( lengths ); // [ 3, 6, 4 ]

В этом случае, мы опять начинаем с объявления массива animals, содержащего список слов. Однако, единственная дополнительная переменная, которую мы используем – это lengths, в которую присваивается результат работы метода map(), который, в свою очередь, возвращает результат работы анонимной функции, принимающей отдельный элемент animal массива animals и возвращающей изменённые данные. Эта анонимная функция производит необходимые для нас операции над каждым элементом массива, который содержится в переменной animal и возвращает результат вычисления длины слова. В итоге, переменная lengths становится массивом той же длины, что и оригинальный массив animals, но содержит количество символов каждого слова.

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

Ещё одно преимущество, которое даёт нам такой подход – это возможность улучшить гибкость кода с помощью именованной функции, что в итоге даст более чистый код. Анонимные функции, написанные в одну строку, могут выглядеть не слишком привлекательно, причем их повторное использование также является затруднительной задачей.

Чтобы решить эти затруднения, мы можем объявить именованную функцию getLength() и использовать её таким образом:

var animals = [ "кот", "собака", "рыба" ];

function getLength( word )
{
  return word.length;
}
console.log( animals.map( getLength ) ); // [ 3, 6, 4 ]

Наш код стал заметно чище, не так ли? Простое использование mapping-техники может вывести ваш код на совершенно другой уровень.

Что такое Функтор (Functor)?

В качестве небольшого отступления, можно также упомянуть тот факт, что добавление возможности использования mapping-техники при работе с объектом array привело к тому, что в ECMAScript 5 обычный тип данных – массив, стал полноценным функтором, что делает функциональное программирование ещё более удобным.

Согласно классическому определению в функциональном программировании, функтор отвечает трём критериям:

  1. Должен содержать набор значений
  2. Должен реализовывать map-функцию для обработки каждого элемента из набора значений
  3. Map-функция должна возвращать функтор такого же размера

Для более подробного ознакомления с данной конструкцией, можно перейти к статье в Википедии: Функциональный объект

Использование метода reduce()

Метод reduce() также является новичком в ECMAScript 5 и в чём-то схож с map(), за исключением того, что производит не новый функтор, а простой результат, который может быть любого типа. Например, представим, что мы бы хотели получить сумму количества символов во всех словах, которые содержатся в массиве animals. По старинке, мы могли бы сделать это так:

var animals = [ "кот", "собака", "рыба" ],
    total   = 0,
    item;

for( var count = 0, loops = animals.length; count < loops; count++ )
{
  item = animals[ count ];
  total += item.length;
}

console.log( total ); // 13

После того, как мы определили начальный массив animals, мы создаём переменную total для конечной суммы и назначаем ей значение, равное нулю. Также, мы создаем переменную item, в которой будет храниться каждая итерация цикла for по массиву animals. И дополнительные переменные count – для счётчика цикла; loops – количество итераций. Затем мы запускаем цикл for, который проходит по каждому элементу массива animals, присваивая переменной item текущий элемент. И под конец, мы прибавляем длину слова к значению, хранящемуся в переменной total.

И опять же, в таком подходе, с технической точки зрения – всё в порядке. Мы начали с создания массива и закончили, получив результат. Но использования метода reduce() позволит сделать наш код более очевидным:

var animals = [ "кот", "собака", "рыба" ];

var total = animals.reduce( function( sum, word )
{
  return sum + word.length;
}, 0 );

console.log( total ); // 13

В этом случае, мы объявили новую переменную total и присвоили ей результат работы метода reduce(), который применили к массиву animal. Метод reduce() принимает два параметра: анонимную функцию и начальное значение, равное нулю. Затем, он проходит по каждому элементу массива, выполняя функцию для текущего элемента, в которой происходит обработка данных. И эти данные доступны на каждой итерации цикла. В нашем случае, анонимная функция принимает два параметра: сумму и то слово из массива, которое будет обработано. Сама же функция просто складывает сумму и длину обрабатываемого слова.

Важным моментом в данном случае является то, что мы установили второй параметр для метода reduce(), равным нулю. Данный параметр будет начальным значением для первого аргумента при первом запуске анонимной функции. Попробуйте самостоятельно изменить это значение, также прописав console.log( sum ); внутри метода reduce(), чтобы на практике понять его работу.

Такой способ может показаться немного более сложным, чем хотелось бы, из-за объявления анонимной функции. Давайте попробуем сделать это снова, но используя именованную функцию, вместо анонимной:

var animals = [ "кот", "собака", "рыба" ];

var addLength = function( sum, word )
{
  return sum + word.length;
};

var total = animals.reduce( addLength, 0 );

console.log( total ); // 13

Получилось немного длинней, но это как раз тот случай, кода больше кода не значит – хуже. В данном случае должно быть более понятным то, что происходит при использовании метода reduce().

В этом случае, метод reduce() принимает два параметра: функцию, которая применяется к каждому элементу массива и начальное значение для возвращаемого результата работы функции. Созданная нами именованная функция addLength() также принимает два параметра: вычисляемую сумму и обрабатываемую строку.

В заключение

Использование методов map() и reduce() на регулярной основе, даст вам возможность сделать код более чистым и универсальным, а также улучшить его поддерживаемость, что является профессиональным подходом к программированию.

Данные методы одни из нескольких, которые были добавлены в ECMAScript 5. По всей вероятности, удовлетворение разработчиков от использования подобных методов превысит тот профит, который может быть получен от обычных способов, таких, как циклы.

При создании статьи были использованы следующие источники:

  1. Using Map and Reduce in Functional JavaScript
  2. Справочник по JavaScript от MDN
Поделиться

Оставить комментарий

Вы можете использовать следующие HTML-теги:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
Обязательно к заполнению