В JavaScript есть методы массивов. пришедшие из функциональных языков. Они получают исходный массив, создают новый пустой массив и заполняют его, применяя к копии значения каждого элемента указанную callback-функцию.
Все функциональные методы массивов имеют схожий синтаксис. Исходный массив array, вызов метода method и callback-функция callback в качестве аргумента метода.
В большинстве методов, аргументами callback-функции являются значение элемента currentValue (первый параметр), позиция элемента index (второй параметр) и сам исходный массив array (третий параметр).
array.method((item, idx, arr) => {
// логика которая будет применяться на каждой итерации
});
Copy
Все параметры, кроме значения элемента item, необязательны. Названия параметров могут быть любые, но есть неофициальные соглашения.
array.method(item => {
// логика которая будет применяться на каждой итерации
});
Copy
2. Array.prototype.forEach()
Единственный из функциональных методов, который ничего не возвращает, а просто перебирает коллекцию. Используется как замена цикла for.
const numbers = [1, 2, 3];
// Классический for
for (let i = 0; i < numbers.length; i += 1) {
console.log(numbers[i]);
}
// Функциональный forEach
numbers.forEach(num => console.log(num));
// Указываем параметр idx если нужен доступ к счетчику
numbers.forEach((num, idx) => console.log(`index ${idx}, value ${num}`));
Copy
Код более декларативный, читабельный, поддерживаемый. Поэтому дальнейшее использование цикла for при выполнении домашних работ не рекомендуется. Единственным местом, где все еще необходимо использовать цикл for, это задача с прерыванием выполнения цикла, forEach прервать инструкцией break нельзя.
3. Array.prototype.map()
Используется для трансформации массива. Применяет callback-функцию к каждому элементу исходного массива, результат работы callback-функции записывает в новый массив, который и будет результатом выполнения метода. Исходный и новый массив всегда имеют одинаковую длину. Не мутирует исходный массив.
Используем map чтобы пройти по базе данных users и получить массив всех имен пользователей.
const users = [
{ name: 'Mango', isActive: true },
{ name: 'Poly', isActive: false },
{ name: 'Ajax', isActive: true },
];
// Для каждого элемента коллекции (user) вернем значение поля name
const names = users.map(user => user.name);
console.log(names); // ["Mango", "Poly", "Ajax"]
Copy
4. Array.prototype.filter()
Применяет callback-функцию к каждому элементу исходного массива и если результат ее выполнения имеет значение true, то копирует значение из исходного массива в новый массив. Исходный и новый массив могут иметь разную длину. Не мутирует исходный массив. Всегда возвращает массив, даже если в нем всего 1 элемент. Если ничего не найдено, вернет пустой массив. Используется, когда необходимо найти более одного элемента в коллекции.
Используем filter, чтобы пройти по базе данных users и выбрать активных и неактивных пользователей по значению свойства isActive.
const users = [
{ name: 'Mango', isActive: true },
{ name: 'Poly', isActive: false },
{ name: 'Ajax', isActive: true },
{ name: 'Chelsey', isActive: false },
];
// Для каждого элемента коллекции (user) проверим поле isActive.
// Если оно true, то текущий элемент (user) будет записан в результирующий массив.
const activeUsers = users.filter(user => user.isActive);
console.log(activeUsers);
// Для каждого элемента коллекции (user) проверим поле isActive.
// Если оно false, то текущий элемент (user) будет записан в результирующий массив.
const inactiveUsers = users.filter(user => !user.isActive);
console.log(inactiveUsers);
Copy
5. Array.prototype.find()
Что делать, если нам нужно найти уникальный id пользователя среди 30000 объектов, а среди 300000?
Первое, что вы хотели бы сделать - это вызвать filter, пройти по коллекции и вернуть только того пользователя, id которого совпадет. Это работает, но какой ценой? Представьте, что нужный нам объект пользователя стоит под индексом 2 в массиве. Какой смысл фильтровать все остальное? Мы теряем производительность из-за лишних операций.
Для таких случаев есть метод find, который будет искать до первого совпадения, после чего прервет свое выполнение.
Применяет callback-функцию к каждому элементу исходного массива и если результат ее выполнения имеет значение true, то возвращает этот элемент и завершает свое выполнение. Если ничего не найдено, вернет undefined.
Используем find, чтобы пройти по базе данных users и найти пользователя по идентификатору id. Идентификаторы всегда уникальны.
const users = [
{ id: '000', name: 'Mango', isActive: true },
{ id: '001', name: 'Poly', isActive: false },
{ id: '002', name: 'Ajax', isActive: true },
{ id: '003', name: 'Chelsey', isActive: false },
];
// Для каждого элемента коллекции (user) проверим поле id.
// Если оно совпадает с искомым идентификатором, то find прекратит
// выполнение и вернет текущий элемент (user) как результат выполнения
const user = users.find(user => user.id === '002');
console.log(user);
// Создадим функцию которая будет возвращать нам пользователя по id
const getUserById = (arr, id) => arr.find(x => x.id === id);
console.log(getUserById(users, '001'));
console.log(getUserById(users, '003'));
Copy
6. Array.prototype.every() и Array.prototype.some()
Метод every проверяет, прошли ли все элементы массива тест, предоставляемый callback-функцией. Возвращает true, если вызов callback-функции вернет true для каждого элемента в array.
Метод some проверяет, проходит ли по крайней мере один элемент в массиве тест, предоставляемый callback-функцией. Возвращает true, если вызов callback-функции вернет true хотя бы для одного элемента в array.
// Функция которая проверяет величину значения, возвращает true или false.
const isBigEnough = val => val >= 10;
// Допустим нам нужно узнать достаточно ли большие ВСЕ числа в коллекции.
// Все что нам нужно это буль true/false. Метод every вернет true только тогда,
// когда все элементы коллекции будут удовлетворять условию в callback-функции.
console.log([12, 5, 8, 130, 44].every(isBigEnough)); // false
console.log([12, 54, 18, 130, 44].every(isBigEnough)); // true
// Допустим нам нужно узнать ЕСТЬ ЛИ в коллекции числа больше 10, хотя бы одно
// или больше. Все что нам нужно это буль true/false. Метод some вернет true
// только тогда, когда хотябы 1 или более элементов коллекции будут
// удовлетворять условию в callback-функции.
console.log([2, 5, 8, 1, 4].some(isBigEnough)); // false
console.log([12, 5, 8, 1, 4].some(isBigEnough)); // true
Copy
Давайте еще пример. Есть массив объектов фруктов, необходимо узнать все ли фрукты есть в наличии и есть ли в наличии хоть какие-то фрукты более 0 штук.
const fruits = [
{ name: 'apples', amount: 100 },
{ name: 'bananas', amount: 0 },
{ name: 'grapes', amount: 50 },
];
// every вернет true только если всех фруктов будет больше чем 0 штук
const allAvailable = fruits.every(fruit => fruit.amount > 0); // false
// some вернет true если хотябы одного фрукта будет больше чем 0 штук
const anyAvailable = fruits.some(fruits => fruits.amount > 0); // true
Copy
7. Array.prototype.reduce()
Аккумулирующая функция, используется для последовательной обработки каждого элемента массива с сохранением промежуточного результата. Швейцарский нож функциональных методов массива. Возможно немного сложна в усвоении, но результат стоит того.
previousValue — промежуточный результат (аккумулятор)
currentItem — текущий элемент массива
index — индекс текущего элемента массива
array — оригинальный массив
Легко представить ее работу на примере подсчета суммы значений элементов массива. Если в качестве callback-функции задать сложение, то reduce вернет сумму всех значений массива.
Работает это так:
Функция reduce создаст новую переменную, называемую аккумулятор. Затем присвоит аккумулятору значение текущего (первого) элемента массива currentItem.
Функция проверит, есть ли у массива следующий элемент и если он есть, то добавит значение к аккумулятору. Когда следующего элемента не окажется (конец массива), функция вернет значения аккумулятора.
Вторым аргументом reduce может быть начальное значение initialValue. Это значение будет присвоено аккумулятору до применения callback-функции к первому элементу массива.
Допустим у нас есть следующая задача: из массива постов твиттера отдельного пользователя необходимо посчитать сумму всех лайков. Можно перебрать циклом for или forEach, каждое из этих решений потребует дополнительного кода. А можно использовать reduce.
const tweets = [
{ id: '000', likes: 5, tags: ['js', 'nodejs'] },
{ id: '001', likes: 2, tags: ['html', 'css'] },
{ id: '002', likes: 17, tags: ['html', 'js', 'nodejs'] },
{ id: '003', likes: 8, tags: ['css', 'react'] },
{ id: '004', likes: 0, tags: ['js', 'nodejs', 'react'] },
];
// Пройдем по всем элементам коллекции и прибавим значения свойства likes
// к аккумулятору, начальное значение которого укажем 0.
const likes = tweets.reduce((totalLikes, tweet) => totalLikes + tweet.likes, 0);
console.log(likes); // 32
// Наверное подсчет лайков не одиночная операция, поэтому напишем функцию
// для подсчета лайков из коллекции
const countLikes = tweets =>
tweets.reduce((totalLikes, tweet) => totalLikes + tweet.likes, 0);
console.log(countLikes(tweets)); // 32
Copy
Заметили свойство tags у каждого поста? Продолжая тему reduce, мы соберем в массив все теги, которые встречаются в постах.
const tweets = [
{ id: '000', likes: 5, tags: ['js', 'nodejs'] },
{ id: '001', likes: 2, tags: ['html', 'css'] },
{ id: '002', likes: 17, tags: ['html', 'js', 'nodejs'] },
{ id: '003', likes: 8, tags: ['css', 'react'] },
{ id: '004', likes: 0, tags: ['js', 'nodejs', 'react'] },
];
// Пройдем по всем элементам коллекции и добавим значения свойства tags
// к аккумулятору, начальное значение которого укажем пустым массивом [].
// На каждой итерации пушим в аккумулятор все элементы tweet.tags и возвращаем его.
const tags = tweets.reduce((allTags, tweet) => {
allTags.push(...tweet.tags);
return allTags;
}, []);
console.log(tags);
// Наверное сбор тегов не одиночная операция, поэтому напишем функцию
// для сбора тегов из коллекции
const getTags = tweets =>
tweets.reduce((allTags, tweet) => {
allTags.push(...tweet.tags);
return allTags;
}, []);
console.log(getTags(tweets));
Copy
После того, как мы собрали все теги из постов, хорошо бы было посчитать количество уникальных тегов в массиве. И снова reduce тут как тут.
const tweets = [
{ id: '000', likes: 5, tags: ['js', 'nodejs'] },
{ id: '001', likes: 2, tags: ['html', 'css'] },
{ id: '002', likes: 17, tags: ['html', 'js', 'nodejs'] },
{ id: '003', likes: 8, tags: ['css', 'react'] },
{ id: '004', likes: 0, tags: ['js', 'nodejs', 'react'] },
];
const getTags = tweets =>
tweets.reduce((allTags, tweet) => {
allTags.push(...tweet.tags);
return allTags;
}, []);
const tags = getTags(tweets);
// Вынесем callback-функцию отдельно, а в reducе передадим ссылку на нее.
// Это стандартная практика если callback-функция довольно большая.
// Если в объекте-аккумуляторе acc нету своего свойства с ключем tag,
// то создаем его и записывает ему значение 0.
// В противном случае увеличиваем значение на 1.
const getTagStats = (acc, tag) => {
if (!acc.hasOwnProperty(tag)) {
acc[tag] = 0;
}
acc[tag] += 1;
return acc;
};
// Начальное значение аккумулятора это пустой объект {}
const countTags = tags => tags.reduce(getTagStats, {});
const tagCount = countTags(tags);
console.log(tagCount);
Copy
8. Array.prototype.sort()
Позволяет сортировать элементы массива на месте. Помимо возврата отсортированного массива, метод sort также отсортирует массив, на котором он был вызван. По умолчанию метод sort сортирует, преобразуя элементы к строке.
Для указания своего порядка сортировки в метод arr.sort(fn) нужно передать функцию fn с двумя параметрами, которая сравнивает их. Внутренний алгоритм функции сортировки умеет сортировать любые массивы. Но для этого ему нужно знать, как их сравнивать. Эту роль и выполняет fn.
Алгоритм сортировки, встроенный в JavaScript, будет передавать ей для сравнения элементы массива. Она должна возвращать:
Положительное значение, если a > b
Отрицательное значение, если a < b
Если равны – можно 0, но вообще не важно, что возвращать, если их взаимный порядок не имеет значения.
Отсортируем массив наших пользователей по возрастанию дней онлайн активности.
Есть массив чисел, из него необходимо взять все четные и умножить на 2, после чего еще и развернуть массив. Выразим кодом решение так, как мы уже умеем это делать.
Вариант решения выше неплох. Проблема в том, что у нас появляются промежуточные переменные после каждой операции. Избавиться от них можно используя цепочки вызовов методов.
Функциональные методы массива можно группировать в цепочки. Каждый следующий метод будет выполняться на результате работы предыдущего.
const numbers = [1, 2, 3, 4, 5, 6, 7, 8];
/*
* Фильтруем четные числа. Далее, на результате метода filter,
* вызываем map и множим на 2. После чего на результате
* метода map вызываем reverse.
*/
const result = numbers
.filter(x => x % 2 === 0)
.map(y => y * 2)
.reverse();
console.log(result);