Метод массива Javascript по сравнению сmap
,filter
,forEach
и другие широко используемые итерационные методы,reduce
Мы часто упускаем из виду, давайте исследовать вместе сегодняreduce
В нашей реальной боевой разработке, какие замечательные применения, следующие изreduce
Грамматика начинается.
грамматика
array.reduce(function(accumulator, arrayElement, currentIndex, arr), initialValue)
Если передается начальное значение, первая итерация аккумулятора является начальным значением, в противном случае это первый элемент массива; последующие итерации будут результатом, возвращаемым предыдущей функцией итерации. Таким образом, если длина массива равна n, если передается начальное значение, количество итераций равно n, в противном случае — n-1.
Например, реализуйте массив arr = [1,2,3,4], чтобы найти сумму массива
let arr = [1,2,3,4];
arr.reduce(function(pre,cur){return pre + cur}); // return 10
На самом деле, сокращение имеет много важных применений, потому что значение аккумулятора не обязательно должно быть простого типа (например, числа или строки), оно также может быть структурированным типом (например, массивом или объектом), который позволяет нам использовать его Делать некоторые другие полезные вещи, такие как:
- преобразовать массив в объект
- расширить больший массив
- Выполнить два расчета за один проход
- Объединение функций карты и фильтра
- Последовательный запуск асинхронных функций
преобразовать массив в объект
В реальной разработке бизнеса вы могли столкнуться с такой ситуацией: вам нужно преобразовать тип массива, возвращаемый фоновым интерфейсом, в объект, который использует значение id в качестве ключа и каждый элемент массива в качестве значения для поиска.
Например:
const userList = [
{
id: 1,
username: 'john',
sex: 1,
email: 'john@163.com'
},
{
id: 2,
username: 'jerry',
sex: 1,
email: 'jerry@163.com'
},
{
id: 3,
username: 'nancy',
sex: 0,
email: ''
}
];
Если вы использовали Lodash Library, используйте_.keyBy
Этот метод можно преобразовать, но сreduce
Это требование также может быть выполнено.
function keyByUsernameReducer(acc, person) {
return {...acc, [person.id]: person};
}
const userObj = userList.reduce(keyByUsernameReducer, {});
console.log(userObj);
// {
// 1: {
// id: 1,
// username: 'john',
// sex: 1,
// email: 'john@163.com'
// },
// 2: {
// id: 2,
// username: 'jerry',
// sex: 1,
// email: 'jerry@163.com'
// },
// 3: {
// id: 3,
// username: 'nancy',
// sex: 0,
// email: ''
// }
// }
Развернуть небольшой массив в большой массив
Рассмотрим сценарий, в котором мы читаем кучу строк простого текста в массив и хотим разделить каждую строку запятыми, чтобы создать больший список массивов.
const fileLines = [
'Inspector Algar,Inspector Bardle,Mr. Barker,Inspector Barton',
'Inspector Baynes,Inspector Bradstreet,Inspector Sam Brown',
'Monsieur Dubugue,Birdy Edwards,Inspector Forbes,Inspector Forrester',
'Inspector Gregory,Inspector Tobias Gregson,Inspector Hill',
'Inspector Stanley Hopkins,Inspector Athelney Jones'
];
function splitLineReducer(acc, line) {
return acc.concat(line.split(/,/g));
}
const investigators = fileLines.reduce(splitLineReducer, []);
console.log(investigators);
// [
// "Inspector Algar",
// "Inspector Bardle",
// "Mr. Barker",
// "Inspector Barton",
// "Inspector Baynes",
// "Inspector Bradstreet",
// "Inspector Sam Brown",
// "Monsieur Dubugue",
// "Birdy Edwards",
// "Inspector Forbes",
// "Inspector Forrester",
// "Inspector Gregory",
// "Inspector Tobias Gregson",
// "Inspector Hill",
// "Inspector Stanley Hopkins",
// "Inspector Athelney Jones"
// ]
Мы начинаем с массива длиной 5 и заканчиваем массивом длиной 16.
Другим распространенным случаем увеличения массива является плоской карта. Иногда нам нужно расширить вторичное массив с помощью способа карты. В это время мы можем использовать уменьшение для достижения уплотнения.
Например:
Array.prototype.flatMap = function(f) {
const reducer = (acc, item) => acc.concat(f(item));
return this.reduce(reducer, []);
}
const arr = ["今天天气不错", "", "早上好"]
const arr1 = arr.map(s => s.split(""))
// [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]]
const arr2 = arr.flatMap(s => s.split(''));
// ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]
Выполнить два расчета за один проход
Иногда нам нужно сделать два вычисления в массиве. Например, мы можем захотеть вычислить максимум и минимум списка чисел. Мы можем сделать это с двумя проходами:
const readings = [0.3, 1.2, 3.4, 0.2, 3.2, 5.5, 0.4];
const maxReading = readings.reduce((x, y) => Math.max(x, y), Number.MIN_VALUE);
const minReading = readings.reduce((x, y) => Math.min(x, y), Number.MAX_VALUE);
console.log({minReading, maxReading});
// {minReading: 0.2, maxReading: 5.5}
Это требует обхода нашего массива дважды. Однако иногда мы можем не хотеть этого делать. Поскольку .reduce() позволяет нам возвращать любой тип, который мы хотим, нам не нужно возвращать числа. Мы можем закодировать два значения в объект. Затем мы можем выполнить два вычисления на каждой итерации и пройти массив только один раз:
const readings = [0.3, 1.2, 3.4, 0.2, 3.2, 5.5, 0.4];
function minMaxReducer(acc, reading) {
return {
minReading: Math.min(acc.minReading, reading),
maxReading: Math.max(acc.maxReading, reading),
};
}
const initMinMax = {
minReading: Number.MAX_VALUE,
maxReading: Number.MIN_VALUE,
};
const minMax = readings.reduce(minMaxReducer, initMinMax);
console.log(minMax);
// {minReading: 0.2, maxReading: 5.5}
Объедините сопоставление и фильтрацию в один процесс
Опять же из предыдущего списка пользователей, мы хотим найти имена пользователей, у которых нет адреса электронной почты, и вернуть их имена пользователей в виде строки с запятыми. Один из способов — использовать три отдельные операции:
- Получить пользователей после фильтрации писем
- Получить список имен пользователей
- объединить имя пользователя
Если сложить их все вместе, это может выглядеть так:
function notEmptyEmail(x) {
return !!x.email
}
function notEmptyEmailUsername(a, b) {
return a ? `${a},${b}` : b
}
const userWithEmail = userList.filter(notEmptyEmail);
const usernameWithEmail = userWithEmail.map((user=> user.username)
const userWithEmailFormatStr = userWithEmail.reduce(notEmptyEmailUsername, '');
console.log(userWithEmailFormatStr);
// 'john,jerry'
Теперь этот код отлично читается, и не должно быть проблем с производительностью для небольших выборочных данных, но что, если у нас есть огромный массив? Если мы изменим обратный вызов редуктора, то сможем сделать все сразу:
function notEmptyEmail(x) {
return !!x.email
}
function notEmptyEmailUsername(usernameAcc, person){
return (notEmptyEmail(person))
? (usernameAcc ? `${usernameAcc},${person.username}` : `${person.username}`) : usernameAcc;
}
const userWithEmailFormatStr = userList.reduce(notEmptyEmailUsername, '');
console.log(userWithEmailFormatStr);
// 'john,jerry'
В этой версии мы просматриваем массив только один раз, обычно рекомендуется использоватьfilter
а такжеmap
Комбинация , рекомендуется, если не обнаружены проблемы с производительностьюreduce
оптимизировать.
Последовательный запуск асинхронных функций
Еще одна вещь, которую мы можем сделать с помощью .reduce(), — это запускать промисы последовательно (а не параллельно). Если у вас есть ограничение скорости запросов API или вам нужно передать результат каждого промиса следующему,reduce
могу помочь тебе.
В качестве примера предположим, что мы хотимuserList
Все в массиве получают сообщение.
function fetchMessages(username) {
return fetch(`https://example.com/api/messages/${username}`)
.then(response => response.json());
}
function getUsername(person) {
return person.username;
}
async function chainedFetchMessages(p, username) {
// In this function, p is a promise. We wait for it to finish,
// then run fetchMessages().
const obj = await p;
const data = await fetchMessages(username);
return { ...obj, [username]: data};
}
const msgObj = userList
.map(getUsername)
.reduce(chainedFetchMessages, Promise.resolve({}))
.then(console.log);
// {glestrade: [ … ], mholmes: [ … ], iadler: [ … ]}
async
Функция возвращает объект Promise, который можно использовать сthen
Метод Добавить функцию обратного вызова. Когда функция выполняется, однажды встретившисьawait
Он вернется к первому, пока не завершится асинхронная операция, после чего следует оператор выполнения после функции in vivo.
Обратите внимание, что здесь мы передаем Promise в качестве начального значения.Promise.resolve()
, наш первый вызов API будет запущен немедленно.
Следующее не используетсяasync
версия синтаксического сахара
function fetchMessages(username) {
return fetch(`https://example.com/api/messages/${username}`)
.then(response => response.json());
}
function getUsername(person) {
return person.username;
}
function chainedFetchMessages(p, username) {
// In this function, p is a promise. We wait for it to finish,
// then run fetchMessages().
return p.then((obj)=>{
return fetchMessages(username).then(data=>{
return {
...obj,
[username]: data
}
})
})
}
const msgObj = peopleArr
.map(getUsername)
.reduce(chainedFetchMessages, Promise.resolve({}))
.then(console.log);
// {glestrade: [ … ], mholmes: [ … ], iadler: [ … ]}
PS: Для получения дополнительной информации о внешнем интерфейсе и технической галантереи, пожалуйста, обратите внимание на публичный аккаунт "Новый горизонт переднего плана"