
Metoda reduce v JavaScriptu
Kdy (ne)použít metodu reduce v JavaScriptu. Praktické příklady a srovnání s alternativami.
Metoda reduce umožňuje redukovat pole na jedinou hodnotu. Je to mocný nástroj, ale v praxi existuje téměř vždy čitelnější alternativa. Tento článek ukazuje, kdy reduce skutečně použít a kdy sáhnout po jiném řešení.
Základní syntaxe
pole.reduce((akumulator, hodnota, index, pole) => {
return novyAkumulator;
}, pocatecniHodnota);
Přestože je počáteční hodnota volitelná, v praxi je téměř vždy lepší ji uvést. Zabráníte tím chování, které je v krajních případech překvapivé (například prázdné pole).
Proč se reduce nadužívá
Metoda reduce se v praxi používá mnohem častěji, než by bylo vhodné. Důvodů je několik:
- Působí „funkcionálně“ — pochází z funkcionálního programování a přišla s vlnou FP spolu s
mapafilter, takže ji lidé automaticky považují za moderní přístup - Tutoriály — často ji prezentují jako „pokročilou techniku“, kterou by měl znát každý JS vývojář
- One-liner syndrom — láká k zápisu všeho na jeden řádek, i když výsledek je nečitelný
- Universálnost — pomocí
reducelze skutečně implementovat všechny ostatní array metody (map,filter,find,some,every,flat…), což vede k dojmu, že je to „správný“ nástroj na vše
Ve skutečnosti je reduce okrajový nástroj pro specifické případy. Pro většinu úloh existuje čitelnější alternativa.
Výhody reduce
Přesto má reduce několik legitimních výhod:
- Výsledek jako const — nepotřebujete
letproměnnou, kterou postupně měníte. To má tu výhodu, že nehrozí, že se dále v kódu omylem přepíše - Zapouzdřený stav — akumulátor neuniká do okolního scope
- Jeden výraz — lze použít přímo v expression kontextu (přiřazení, return, ternární operátor)
- Žádné mezivýsledky — na rozdíl od
filter().map()nevytváří mezipole
Výkonnost: Samotný reduce je kvůli režii volání funkce o něco pomalejší než prostý for cyklus. Výhodu má při nahrazení řetězených metod (filter().map()), kde ušetří vytváření mezipole a druhý průchod. V praxi je rozdíl obvykle tak malý, že má větší smysl řešit čitelnost.
Kdy reduce nepoužívat
Ve většině případů existuje kratší a čitelnější alternativa:
Maximum a minimum
Pokud použijete reduce, vždy dejte počáteční hodnotu, ať se vám chování nerozpadne na prázdném poli.
const cisla = [3, 7, 2, 9, 1];
// S reduce
const max = cisla.reduce((a, b) => a > b ? a : b);
// Bez reduce — kratší a jasnější
const max = Math.max(...cisla);
const min = Math.min(...cisla);
Zploštění pole
const vnorene = [[1, 2], [3, 4], [5, 6]];
// S reduce
const ploche = vnorene.reduce((acc, arr) => acc.concat(arr), []);
// Bez reduce — kratší
const ploche = vnorene.flat();
Seskupování dat
const lide = [
{ jmeno: "Anna", vek: 25 },
{ jmeno: "Petr", vek: 30 },
{ jmeno: "Jana", vek: 25 }
];
// S reduce — 6 řádků
const podleVeku = lide.reduce((acc, osoba) => {
const klic = osoba.vek;
if (!acc[klic]) acc[klic] = [];
acc[klic].push(osoba);
return acc;
}, {});
// Bez reduce — 1 řádek (moderní prohlížeče)
const podleVeku = Object.groupBy(lide, o => o.vek);
Poznámka: Object.groupBy je relativně nová funkce, takže ji používejte jen tam, kde máte jistotu podpory v cílovém prostředí (nebo máte transpiling/polyfill).
Filtrování a mapování
Vyfiltruje sudá čísla a zdvojnásobí je — výsledek je [4, 8, 12].
const cisla = [1, 2, 3, 4, 5, 6];
// S reduce
const vysledek = cisla.reduce((acc, n) => {
if (n % 2 === 0) acc.push(n * 2);
return acc;
}, []);
// Bez reduce — čitelnější
const vysledek = cisla.filter(n => n % 2 === 0).map(n => n * 2);
Počítání výskytů
const ovoce = ["jablko", "banán", "jablko", "banán", "jablko"];
// S reduce
const pocty = ovoce.reduce((acc, o) => {
acc[o] = (acc[o] || 0) + 1;
return acc;
}, {});
// Bez reduce — podobná délka, ale přímočařejší
const pocty = {};
for (const o of ovoce) {
pocty[o] = (pocty[o] || 0) + 1;
}
Kdy reduce skutečně použít
Součet čísel
const cisla = [1, 2, 3, 4, 5];
// S reduce
const soucet = cisla.reduce((acc, n) => acc + n, 0);
// Bez reduce
let soucet = 0;
for (const n of cisla) soucet += n;
Výsledek s reduce je const a pomocná proměnná neuniká do scope.
Skládání funkcí (compose/pipe)
const pricti5 = x => x + 5;
const vynasob2 = x => x * 2;
const odecti3 = x => x - 3;
// S reduce
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
// pipe: funkce se volají zleva doprava
pipe(pricti5, vynasob2, odecti3)(10); // ((10 + 5) * 2) - 3 = 27
// compose: funkce se volají zprava doleva
compose(odecti3, vynasob2, pricti5)(10); // ((10 + 5) * 2) - 3 = 27
// Bez reduce — vnořené volání
odecti3(vynasob2(pricti5(10))); // ((10 + 5) * 2) - 3 = 27
// Bez reduce — cyklem
const pipe = (...fns) => x => {
let result = x;
for (const fn of fns) result = fn(result);
return result;
};
Vnořené volání je při více funkcích nečitelné. Cyklus funguje, ale reduce je zde nejelegantnější.
Shrnutí
- Nepoužívejte
reducepro jednoduché operace —map,filter,flat,Math.maxbývají čitelnější - Používejte
reducepro součty a skládání funkcí (pipe/compose) - Vždy uvádějte počáteční hodnotu — bez ní
reducena prázdném poli vyhodíTypeError
Odkazy
Související články
Jak vkládat 3D objekty na web pomocí Three.js
Které formáty použít, jak vytvářet modely pomocí AI a kdy raději použít obrázek nebo video.
JavaScript null a undefined
Rozdíly mezi null a undefined v JavaScriptu, kdy je používat a jak se vyhnout běžným chybám.
Sleep v JavaScriptu
Jak implementovat sleep/delay funkcionalitu v JavaScriptu pomocí Promise a async/await