Такие методы массивов, как forEach()
, map()
, filter()
, reduce()
появились еще в спецификации ES5, так что на данный момент они прочно вошли в стандартный набор средств JavaScript-программиста. Однако на первоначальном этапе эти методы временами вызывают недопонимание. Непонятным в них является, как правило, механизм работы callback-функции, которая запускается в каждом методе, причем с некоторыми отличиями. Давайте разберемся на примере цикла for(), как работает функция-коллбек.
Метод forEach()
Синтаксис метода forEach()
подразумевает, что мы можем передать в функцию-коллбек, которая вызывается для каждого элемента, от одного до трех параметров:
1 2 3 4 5 6 7 | arr.forEach(function(элемент, индекс, массив) { вывод элемента; }) //или с помощью стрелочной функции arr.forEach( (элемент, индекс, массив) => вывод элемента ) |
Как правило, метод array.forEach()
используется для перебора элементов массива с целью вывода какой-либо информации. Например, у нас есть массив строк с именами и фамилиями студентов, и нам нужно вывести их в виде нумерованного списка.
1 2 3 4 5 6 7 8 9 10 | const students =["Иванов Михаил", "Оленев Максим", " Туманов Дмитрий", "Фурманов Алексей"]; document. write ('<ol>'); //обычная функция students.forEach ( function(oneStudent) { document.write(`<li>${ oneStudent } </li>`); }); //или стрелочная функция - что-то одно students.forEach ( oneStudent => document.write(`<li>${ oneStudent } </li>`) ); document.write('</ol>'>); |
Пример в действии:
Что можно понять из кода выше? Внутри метода forEach()
запускается либо обычная, либо стрелочная функция с одним аргументом oneStudent
, который при каждом вызове этого метода указывает на очередного студента из массива students
, то есть в переменной oneStudent
находится значение каждого элемента массива students
. Как происходит вызов функции? Примерно так же, как и внутри цикла for()
ниже, но в цикле переменная oneStudent
заменена на i-тый элемент массива students
, что выглядит в коде, как students[i]
:
1 2 3 4 5 6 | document.write ('<ol>'); for(let i=0; i<students. length; i++) { document.write(`<li>${ students[i] } </li>` ); } document.write('</ol>'); |
Давайте посмотрим на результат работы цикла for
и сравним 2 примера:
Как можно заметить, списки ничем не отличаются, т.к. механизм работы подобен.
Метод forEach() с двумя аргументами
Поскольку метод forEach()
подразумевает, что в него можно передать от одного до трёх аргументов (элемент массива, индекс элемента массива и сам массив), то мы можем воспользоваться не одним, а двумя аргументами, чтобы увидеть, что и в этом случае способы похожи.
1 2 3 4 5 | document. write ('<ol>'); students.forEach ( function (oneStudent, i) { document.write(`<li>${ students[i] } </li>`); }); document.write('</ol>'); |
В коде выше мы видим тот же синтаксис, что и в цикле for()
для обращения к каждому элементу массива (students [i]
). Хотя, на мой взгляд, использование переменой oneStudent
для доступа к значению элемента массива намного удобней.
Ниже вы увидите результат работы кода.
Используем для замены метода forEach() цикл for..of
Ещё один способ, пожалуй, даже более похожий на запись функции-колбека в методе forEach()
дает нам цикл for...of
:
1 2 3 4 5 | document.write ('<ol>'); for(let oneStudent of students){ document.write(`<li>${ oneStudent } </li>` ); } document.write('</ol>'); |
В этом примере в синтаксисе цикла используется такая же переменная oneStudent
, как и в методе forEach()
. В обоих случаях это не что иное, как способ добраться к значению каждого элемента массива, но в методе forEach()
это происходит при вызове функции-коллбека для каждого элемента массива, а в цикле for...of
- при каждой итерации цикла.
И ещё один результат:
И здесь мы видим, что список сформирован точно так же.
Все 4 варианта перебора массива для вывода его элементов в виде списка представлены в примере ниже. Сравните ещё раз, если есть необходимость.
See the Pen for for..of vs array.forEach() by Elen (@ambassador) on CodePen.
forEach()
ничего не возвращает (точнее возвращает undefined
) в отличие от методов map()
и filter()
, которые возвращают новый массив на основе исходного, и метода reduce()
, который возвращает одно значение, сформированное на основе обхода всех элементов массива.Метод map()
Наименование метода map()
переводится, как "карта" с английского языка. С его помощью выполняется обход всех элементов массива с целью их изменения, и формируется новый массив из измененных элементов. То есть после использования метода map()
мы получаем новый массив с новыми значениями на основе значений элементов исходного массива, но при этом исходный массив не изменяется.
1 2 3 4 5 6 7 | let mapArray = arr.map(function(элемент, индекс, массив) { return новый_элемент; }) //или с помощью стрелочной функции let arrayMap = arr.map( (элемент, индекс, массив) => новый_элемент ) |
Из кода видно, что, во-первых, используется оператор return
для помещения каждого нового элемента в новый массив при вызове функции-коллбека, а во-вторых, при вызове метода map()
вам нужно объявить какую-то переменную, в которую будет записан новый массив, сформированный на основе исходного. На картинке ниже в новый массив попадают элементы, увеличенные на 1.
1 2 3 4 5 6 7 8 9 10 11 12 | let numbers = [1,2,3,4,5,6,7]; //обычная функция внутри map() let mapArr = numbers.map(function(value) { return value+1 }); //стрелочная функция внутри map() let mapArr = numbers.map(value => value+1); document.write(`Исходный массив: ${numbers.join(" | ")} <br>Новый массив: ${mapArr.join(" | ") }`); |
Результат выполнения кода:
Заменяем код метода map()
циклом for()
:
1 2 3 4 5 6 7 8 | let numbersForMap = []; for(let i=0; i<numbers.length; i++){ numbersForMap.push( numbers[i]+1 ); } document.write(`Исходный массив: ${numbers.join(" / ")}<br> Новый массив: ${numbersForMap.join(" / ") }`); |
Все достаточно просто + работает корректно + результат такой же, как и с помощью метода map()
.
Метод filter()
Метод array.filter()
предназначен для фильтрации массива по определенному принципу или, точнее, условию. Причём результатом фильтрации является создание нового массива на основе исходного. То есть метод filter()
не только фильтрует исходный массив, но и возвращает отфильтрованный массив в качестве значения. Если элемент массива соответствует проверяемому условию, то он попадает в результирующий массив, если же нет, то будет пропущен. Поэтому в методе filter()
всегда присутствует условие, которое всегда должно вернуть либо true
(и элемент попадет в новый массив), либо false
(и этого элемент не будет в новом массиве).
Синтаксис метода filter()
:
1 2 3 | let новый_массив = исходный_массив.filter(function(элемент, индекс, массив) { return true или false после проверки условия; }) |
Отсюда можно сделать вывод, что после выполнения фильтрации мы получим меньший массив, чем исходный. Этим метод filter()
отличается от метода map()
, в котором в новый массив попадают все элементы исходного, но с предварительным изменением значений.
Например, у нас есть некий массив чисел от 1 до 7, который нужно отфильтровать таким образом, чтобы в новый массив попали только те значения, которые больше 5. Код с методом filter()
:
1 2 3 4 5 6 7 8 9 10 | let numbers = [1,2,3,4,5,6,7]; let copy = numbers.filter(function(value) { return value>5; }); let copy = numbers.filter(value => value>5); document.write(`Исходный массив: ${numbers}<br> Отфильтрованный массив: ${copy}`); |
Результат:
Код для выполнения этой операции с циклом for
будет такой:
1 2 3 4 5 6 | let filterNum= []; for(let i=0; i<numbers.length; i++){ if(numbers[i]>5) filterNum.push( numbers[i] ); } document.write(`Исходный массив: ${numbers}<br> Отфильтрованный массив: ${filterNum}`); |
Давайте посмотрим на результат:
Чаще всего фильтровать приходится не простые массивы, а массивы объектов, так как в них, как правило, содержаться те элементы, которые имеют разные свойства, по которым массивы могут выводится в тело документа. Например, вывод товаров магазина в зависимости от выбора нескольких критериев пользователем. Ниже мы рассмотрим пример фильтрации массива объектов купленных продуктов из общего списка продуктов.
See the Pen filter Array of objects JS by Elen (@ambassador) on CodePen.
Метод reduce()
Синтаксис метода:
1 2 3 | array.reduce(function(промежуточный_результат, элемент, индекс, массив) { return новый_промежуточный_результат; }, начальное_значение); |
В отличие от остальных методов array.reduce()
собирает (или уменьшает, если использовать перевод англ. слова reduce на русский язык) до одного значения значения всех элементы массива.
Примечание: изображения взяты из статьи .map(), filter() & .reduce() — animated
Как правило, метод reduce()
используют для сложения значений числовых массивов. В примере с картинки у нас есть массив, в котором сумма чисел составляет 1+2+3+4+5+6+7 = 28. Давайте получим это значение с помощью метода reduce()
и цикла for()
.
1 2 3 4 5 6 7 8 9 | let numbers = [1,2,3,4,5,6,7]; let sum = numbers.reduce( function(value, accumulator) { return accumulator + value; }); //или let sum = numbers.reduce( (value, accumulator) => accumulator +value); document.write(`Сумма чисел массива [${numbers.join(', ')}] = ${sum}`); |
Проверяем работу метода:
Используем цикл for
для замены метода reduce()
:
Подсчет суммы привел к точно таким же результатам.
Учтите, что в метод reduce()
можно передать начальное значение, которое необходимо учитывать в коде. То есть при его отсутствии подсчет начинается с 0-го элемента массива, а при наличии - именно с этого значения:
1 2 3 4 | let sum1 = numbers.reduce( function(value, accumulator) { return accumulator +value; }, 200); document.write(`Сумма чисел массива [${numbers.join(', ')}] +200 = ${sum1}`); |
Проверяем:
Возможности использования метода array.reduce()
Как правило, с помощью метода reduce()
рассчитывают сумму или, реже, разность значений элементов числовых массивов. Однако, мы также можем найти минимальное и максимальное число в массиве, используя этот метод:
1 2 3 4 5 6 7 8 9 | let myNumbers = [10,-2,34,99,5,-16,17]; let sum = myNumbers.reduce((elem1, elem2) => elem1 + elem2 ); console.log(sum); // 147 let dif = myNumbers.reduce((elem1, elem2) => elem1 - elem2 ); console.log(dif); //-127 let max = myNumbers.reduce((elem1, elem2) => elem1 > elem2 ? elem1 : elem2 ); console.log(max); //99 let min = myNumbers.reduce((elem1, elem2) => elem1 < elem2 ? elem1 : elem2 ); console.log(min); //-16 |
В коде выше представлены стрелочные функции в качестве коллбеков для работы с массивом. Переменые elem1
и elem2
обозначают сначала нулевой и первый элементы массива, а затем результат выполнения действия и следующий элемент массива.
Пример с использованием методов map() и reduce() для подсчета суммы значений в ячейках таблицы
Используем таблицу с числовыми значениями для подсчета суммы чисел в нескольких ячейках.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | <table id="cart"> <caption>Стоимость товаров по размерам</caption> <thead> <tr> <th>№ п/п</th> <th>Название</th> <th>Цена, грн</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>Юбка в клетку</td> <td>400</td> </tr> <tr> <td>2</td> <td>Брюки в полоску</td> <td>580</td> </tr> <tr> <td>3</td> <td>Симпатичное платье с принтом</td> <td>390</td> </tr> <tr> <td>4</td> <td>Сумочка с блестками</td> <td colspan="4">480</td> </tr> </tbody> <tfoot> <tr> <td colspan="2">Итого</td> <td></td> </tr> </tfoot> </table> <script> let cart = document.getElementById("cart"), lastTd = cart.querySelectorAll('tbody td:last-child'), resTd = cart.querySelector('tfoot td:last-child'); lastTd = Array.from(lastTd); const lastTdValues = lastTd.map( td => +td.textContent) resTd.textContent = lastTdValues.reduce((prev, next) =>{ console.log(prev, next); // 400 580 | 980 390 | 1370 480 return prev+next; }) </script> |
В коде выше мы получаем доступ ко всем последним ячейкам таблицы с id="cart"
(переменная lastTd
) и к последней пустой ячейке в tfoot
. Затем для того чтобы иметь возможность использовать методы массивов, мы преобразуем коллекцию html-элементов в обычный массив с помощью метода Array.from()
.
В переменную lastTdValues
с помощью метода map()
мы помещаем в виде массива значения из последних ячеек, преобразуя их в числа. Затем в последнюю ячейку мы поместим результат сложения всех элементов массива lastTdValues
с помощью свойства textContent
.
Проверяем, как работает код:
№ п/п | Название | Цена, грн | |||
---|---|---|---|---|---|
1 | Юбка в клетку | 400 | |||
2 | Брюки в полоску | 580 | |||
3 | Симпатичное платье с принтом | 390 | |||
4 | Сумочка с блестками | 480 | |||
Итого |
Неочевидное применение метода reduce(), а также методов map() и filter()
В примере ниже мы используем метод reduce()
для формирования html-кода в виде списка или нескольких div-ов. В этом случае мы будем использовать уже не массив чисел, а массив объектов, в котором собрана информация о книгах. Каждый элемент массива сожержит одинаковые свойства, описывающие title
- название книги, author
- автора книги, cover
- ссылку на фото обложки, price
- стандартную цену книги, sale
- цену по распродаже и inStock
- наличие на складе. В коде ниже вы найдете часть объектов, весь список - в примере с codepen.io.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | const books = [{ title: "Коллекционер", author: "Фаулз Дж.", cover: 'books/b0.jpg', price: 148, sale: null, inStock: true },{ title: "Я слежу за тобой", author: "Дрисколл Т.", cover: 'books/b1.jpg', price: 213, sale: 189, inStock: true }. { ... }, { title: "Точка обмана", author: "Браун Д.", cover: 'books/b9.jpg', price: 183, sale: null, inStock: false } ]; |
Для того чтобы вывести список книг с помощью метода reduce()
, можно использовать такой код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function printBookList(arr){ const list = arr.reduce(function (html, elem) { console.log(html, elem); html += '<li>' + elem + '</li>'; return html; }, '<ol>') + '</ol>'; return list; } //document.write( printBookList(books) ); //сокращенный вариант записи функции с использованием стрелочной функции в методе reduce() function printBookListMin(arr){ return arr.reduce((html, elem) => html += '<li>' + elem + '</li>', '<ol>') + '</ol>'; } |
Здесь мы формируем список из элементов массива arr
, передаваемого в функцию printBookList(arr)
, используя возможность метода array.reduce()
формировать на основе всех элементов массива один конечный результат, заданный константой list
. Таким результатом станет строка в виде переменной html
, в которую мы помещаем начальное значение '<ol>'
, а затем при каждом вызове функции для каждого элемента массива (переменная elem
) добавляем к переменной html
строку '<li>' + elem + '</li>'
, формируя таким образом нумерованный список из всех элементов массива. После обхода в функции всех элементов массива мы завершаем формирования нумерованного списка, добавляя уже к константе list
завершающий список тег '</ol>'
.
Если мы выведем в окно документа список из наших книг с помощью document.write( printBookList(books) );
то увидим список из объектов, что не очень презентабельно.
Значительно интересней использовать эту функцию для вывода списка элементов для массивов с числовыми или строковыми значениями. Поэтому мы используем метод array.filter()
для того, чтобы отфильтровать массивы по определенному признаку, а затем методом array.map()
сформировать строку из имени автора и названия книги для нового массива. Все это запишем в виде функции, а затем вызовем эту функцию, чтобы узнать, сколько и какие книги из массива продаются по распродаже (свойство sale
не имеет значение null
), и какие книги есть на складе (inStock: true
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function bookFilter(type){ return books.filter(function (book) { return book[type]; }).map(function (book) { return book.author+" "+book.title; }); } const saleBooks = bookFilter('sale'); document.write('<h2>Книги по распродаже</h2>'); //document.write(saleBooks.join("<br>")); document.write( printBookList(saleBooks) ); const inStockBooks = bookFilter('inStock') document.write('<h2>Книги в наличии</h2>'); //document.write(inStockBooks.join("<br>")); document.write( printBookListMin(inStockBooks) ); |
В коде выше мы используем вывод новых массивов с помощью функции printBookList()
и printBookListMin()
. Работают они одинаково при несколько разном синтаксисе за счет использования в функции printBookListMin()
стрелочной функции-коллбека для метода reduce()
.
Результатом использования функций в этот раз будут 2 вполне читабельных списка:
Книги по распродаже
- Дрисколл Т. Я слежу за тобой
- Дойл А. Рассказы о Шерлоке Холмсе
- А.Дж. Финн Женщина в окне
- Владимирская А. Игра на выживание
Книги в наличии
- Фаулз Дж. Коллекционер
- Дрисколл Т. Я слежу за тобой
- Дойл А. Рассказы о Шерлоке Холмсе
- Акунин Б. Планета Вода.
- А.Дж. Финн Женщина в окне
- Владимирская А. Игра на выживание
Вы можете сравнить список книг, выводимый с помощью метода reduce()
списком в html или с помощью метода array.join()
с разделителем в виде <br>
, расскомментировав строки 11 и 15.
Вывод списка книг с использованием всех свойств
Для того чтобы вывести список книг с названиями, авторами, обложкой, стандартной ценой и ценой распродажи, если таковая имеется, а также с указанием наличия книги на складе, нужно создать функцию, которая бы учитывала все особенности именно этого типа объектов. Для этой цели мы опять воспользуемся методом reduce()
, но будем формировать уже не нумерованный список, а несколько div-ов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function printBookListAll(arr, currency="грн."){ const block = arr.reduce(function (html, elem) { let sale = elem.sale ? `<div class="price"><del>${elem.price} ${currency}</del> <ins class="sale">${elem.sale} ${currency}</ins></div>` : `<div class="price">${elem.price} ${currency}</div>`; let inStockClass = elem.inStock ? '': " not-available"; html += ` <div class="product${inStockClass}"> <div class="cover"><img src="${elem.cover}" alt="${elem.title}"></div> <h3>${elem.title}</h3> <div class="author">${elem.author}</div> ${sale} <div class="stock">${elem.inStock ? "Есть на складе" : "Нет на складе"}</div> </div>`; return html; }, '<div class="wrapper">') + '</div>'; return block; } //вызов функции document.write( printBookListAll(books) ); |
Именно с вызова данной функции начинается пример ниже. Он также требует дополнения в виде css-стилей. В нем видно, что те книги, которых нет в наличии, выводятся полупрозрачными и с соответствующей записью после цены.
See the Pen Array map(), filter(), reduce() by Elen (@ambassador) on CodePen.
Пример использования методов forEach(), map(), filter() и reduce() для вывода списка пользователей
Здесь вы найдете использование перечисленных методов массивов + метода sort() для манипуляции данными в массивах простых объектов. Проанализируйте функции в коде для того, чтобы понять, как работают все методы.
See the Pen Array Mehods by Elen (@ambassador) on CodePen.