Ich habe kürzlich jemanden betreut, der Schwierigkeiten mit der .reduce()-Methode in JavaScript hatte. Nämlich, wie man von diesem
const nums = [1, 2, 3]
let value = 0
for (let i = 0; i < nums.length; i++) {
value += nums[i]
}
...zu diesem:
const nums = [1, 2, 3]
const value = nums.reduce((ac, next) => ac + next, 0)
Sie sind funktional äquivalent und summieren beide alle Zahlen im Array auf, aber es gibt einen kleinen Paradigmenwechsel zwischen ihnen. Betrachten wir die Reducer für einen Moment, denn sie sind mächtig und wichtig für Ihre Programmierung. Es gibt buchstäblich Hunderte von anderen Artikeln über Reducer, und ich werde einige meiner Favoriten am Ende verlinken.
Was ist ein Reducer?
Das Erste und Wichtigste, was man über einen Reducer verstehen muss, ist, dass er immer nur einen Wert zurückgibt. Die Aufgabe eines Reducers ist es zu reduzieren. Dieser eine Wert kann eine Zahl, ein String, ein Array oder ein Objekt sein, aber es wird immer nur einer sein. Reducer sind für viele Dinge wirklich großartig, aber sie sind besonders nützlich, um eine gewisse Logik auf eine Gruppe von Werten anzuwenden und mit einem anderen einzelnen Ergebnis zu enden.
Das ist das andere, was man erwähnen muss: Reducer verändern per Natur aus nicht Ihren Anfangswert; vielmehr geben sie etwas anderes zurück. Gehen wir das erste Beispiel durch, damit Sie sehen können, was hier passiert. Das Video unten erklärt es
Es mag hilfreich sein, das Video anzusehen, um zu sehen, wie der Ablauf erfolgt, aber hier ist der Code, den wir uns ansehen
const nums = [1, 2, 3]
let value = 0
for (let i = 0; i < nums.length; i++) {
value += nums[i]
}
Wir haben unser Array (1, 2, 3) und den ersten Wert, zu dem jede Zahl im Array addiert wird (0). Wir gehen die Menge des Arrays durch und addieren sie zum Anfangswert.
Versuchen wir das mal etwas anders
const nums = [1, 2, 3]
const initialValue = 0
const reducer = function (acc, item) {
return acc + item
}
const total = nums.reduce(reducer, initialValue)
Jetzt haben wir dasselbe Array, aber diesmal verändern wir den ersten Wert nicht. Stattdessen haben wir einen initialValue, der nur am Anfang verwendet wird. Als Nächstes können wir eine Funktion erstellen, die einen Akkumulator und ein Element aufnimmt. Der Akkumulator ist der gesammelte Wert, der im letzten Aufruf zurückgegeben wird und der Funktion mitteilt, was der nächste Wert sein wird, zu dem addiert wird. In diesem Fall der Addition können Sie es sich wie einen Schneeball vorstellen, der einen Berg hinunterrollt und jeden Wert auf seinem Weg aufnimmt, während er mit jedem aufgenommenen Wert größer wird.

Wir verwenden .reduce(), um die Funktion anzuwenden und von diesem Anfangswert aus zu starten. Dies kann mit einer Pfeilfunktion verkürzt werden
const nums = [1, 2, 3]
const initialValue = 0
const reducer = (acc, item) => {
return acc + item
}
const total = nums.reduce(reducer, initialValue)
Und dann noch weiter verkürzt! Implizite Rückgaben sind der Hit!
const nums = [1, 2, 3]
const initialValue = 0
const reducer = (acc, item) => acc + item
const total = nums.reduce(reducer, initialValue)
Jetzt können wir die Funktion direkt dort anwenden, wo wir sie aufgerufen haben, und wir können auch diesen Anfangswert direkt hineinlegen!
const nums = [1, 2, 3]
const total = nums.reduce((acc, item) => acc + item,
Ein Akkumulator kann ein einschüchternder Begriff sein, also können Sie ihn sich wie den aktuellen Zustand des Arrays vorstellen, während wir die Logik auf die Aufrufe der Callback-Funktion anwenden.
Der Call Stack
Falls nicht klar ist, was passiert, lassen Sie uns ausgeben, was bei jeder Iteration passiert. Der Reduce-Vorgang verwendet eine Callback-Funktion, die für jedes Element im Array ausgeführt wird. Die folgende Demo hilft, dies klarer zu machen. Ich habe auch ein anderes Array ([1, 3, 6]) verwendet, da die Zahlen gleich dem Index sein könnten, was verwirrend sein könnte.
Siehe den Pen showing acc, item, return von Sarah Drasner (@sdras) auf CodePen.
Wenn wir das ausführen, sehen wir diese Ausgabe in der Konsole
"Acc: 0, Item: 1, Return value: 1"
"Acc: 1, Item: 3, Return value: 4"
"Acc: 4, Item: 6, Return value: 10"
Hier ist eine visuellere Aufschlüsselung:
- Es zeigt, dass der Akkumulator mit unserem Anfangswert,
0, beginnt - Dann haben wir das erste Element, das 1 ist, also ist unser Rückgabewert
1(0 + 1 = 1) 1wird bei der nächsten Ausführung zum Akkumulator- Jetzt haben wir
1als Akkumulator und 3 ist das Element, da es das nächste im Array ist. - Der zurückgegebene Wert wird
4(1 + 3 = 4) - Das wird wiederum zum Akkumulator und das nächste Element bei der Ausführung ist
6 - Das ergibt
10(4 + 6 = 10) und ist unser Endwert, da6die letzte Zahl im Array ist
Einfache Beispiele
Nun, da wir das drauf haben, schauen wir uns einige gängige und nützliche Dinge an, die Reducer tun können.
Wie viele X haben wir?
Nehmen wir an, Sie haben ein Array von Zahlen und möchten ein Objekt zurückgeben, das die Häufigkeit dieser Zahlen im Array meldet. Beachten Sie, dass dies genauso gut für Strings gelten könnte.
const nums = [3, 5, 6, 82, 1, 4, 3, 5, 82]
const result = nums.reduce((tally, amt) => {
tally[amt] ? tally[amt]++ : tally[amt] = 1
return tally
}, {})
console.log(result)
Siehe den Pen simplified reduce von Sarah Drasner (@sdras) auf CodePen.
Moment mal, was haben wir da gerade gemacht?
Anfänglich haben wir ein Array und das Objekt, in das wir seine Inhalte legen werden. In unserem Reducer fragen wir: Existiert dieses Element? Wenn ja, lassen Sie es uns inkrementieren. Wenn nicht, fügen Sie es hinzu und setzen Sie es auf 1. Geben Sie am Ende die gezählte Anzahl jedes Elements zurück. Dann führen wir die Reduce-Funktion aus und übergeben sowohl den Reducer als auch den Anfangswert.
Nehmen Sie ein Array und wandeln Sie es in ein Objekt um, das einige Bedingungen anzeigt
Nehmen wir an, wir haben ein Array und möchten ein Objekt basierend auf einer Reihe von Bedingungen erstellen. Reduce kann dafür hervorragend geeignet sein! Hier möchten wir aus jeder Instanz einer Zahl im Array ein Objekt erstellen und sowohl eine ungerade als auch eine gerade Version dieser Zahl anzeigen. Wenn die Zahl bereits gerade oder ungerade ist, dann ist das, was wir im Objekt haben werden.
const nums = [3, 5, 6, 82, 1, 4, 3, 5, 82]
// we're going to make an object from an even and odd
// version of each instance of a number
const result = nums.reduce((acc, item) => {
acc[item] = {
odd: item % 2 ? item : item - 1,
even: item % 2 ? item + 1 : item
}
return acc
}, {})
console.log(result)
Siehe den Pen simplified reduce von Sarah Drasner (@sdras) auf CodePen.
Dies gibt die folgende Ausgabe in der Konsole aus
1:{odd: 1, even: 2}
3:{odd: 3, even: 4}
4:{odd: 3, even: 4}
5:{odd: 5, even: 6}
6:{odd: 5, even: 6}
82:{odd: 81, even: 82}
Okay, was passiert also?
Während wir jedes Element im Array durchgehen, erstellen wir eine Eigenschaft für gerade und ungerade, und basierend auf einer Inline-Bedingung mit einem Modulo-Operator speichern wir entweder die Zahl oder inkrementieren sie um 1. Der Modulo-Operator ist dafür sehr gut geeignet, da er schnell auf gerade oder ungerade prüfen kann – wenn sie durch zwei teilbar ist, ist sie gerade, wenn nicht, ist sie ungerade.
Weitere Ressourcen
Oben habe ich auf andere Artikel verwiesen, die nützliche Ressourcen sind, um sich mit der Rolle von Reducern vertraut zu machen. Hier sind einige meiner Favoriten
- Die MDN-Dokumentation ist dafür wunderbar. Ehrlich gesagt, es ist einer ihrer besten Beiträge, meiner Meinung nach. Sie beschreiben auch etwas detaillierter, was passiert, wenn Sie keinen Anfangswert angeben, was wir in diesem Beitrag nicht behandelt haben.
- Daniel Shiffman erklärt Dinge immer erstaunlich auf Coding Train.
- A Drip of JavaScript macht das auch gut.
Ich frage mich, ob es möglich wäre, den Weg ohne .reduce() zu zeigen und dann die Leistungsunterschiede aufzuzeigen. Ich würde gerne wissen, wie Menschen, wenn ihnen die beiden verschiedenen Optionen präsentiert werden, den Zweck des Codes intuitiv erfassen.
Hallo David! Der Leistungsteil ist ein großartiger Punkt, den Sie ansprechen, denn Sie intuitiv richtig – es gibt Zeiten, in denen Funktionalität und
.reduce()nicht so performant sind und nicht das richtige Werkzeug für den Job sind. Ich werde sehen, ob ich etwas finden kann, das das veranschaulicht. Das Problem bei Benchmarks ist jedoch, dass sie tendenziell eine etwas vage Logik haben – die genaue Implementierung spielt wirklich eine Rolle, daher könnte es Leute auf den falschen Weg führen, zu denken, dass eine immer besser ist als die andere in jeder Situation. Tolle Beobachtung!Ich liebe das Video – eine großartige Möglichkeit, zu visualisieren, wie Reducer funktionieren!
Ein weiterer Vorteil von Reducern gegenüber for-Schleifen ist, dass sie viel lesbarer sein können, besonders wenn sie benannt sind.
Verwenden Sie Ihr
sum-Beispiel,Das ist ein toller Punkt, Adam!
Ich stimme dem Punkt über die Videos, die zeigen, wie reduce funktioniert, nur zu. Sie waren großartig. Für Leute, die eher visuell lernen, kann dies ihnen wirklich helfen, das präsentierte Konzept zu verstehen. Danke, dass Sie sich die zusätzliche Zeit genommen haben, die Videos zu erstellen.
Ich stimme Adam zu! Die Lesbarkeit ist großartig. Meine bevorzugte Methode, diese zu schreiben, ist wie Sie es hier getan haben... wo die Reducer-Funktion eine benannte Funktion ist, die an
[].reduce()übergeben wird.Lesbarkeit ist wichtig
Danke Sarah, das macht Sinn für die reduce-Methode, etwas, das ich bisher noch nicht viel benutzt habe. Wenn ich mir die MDN-Dokumentation dafür ansehe, wäre die Verwendung zum Abflachen von Arrays sehr nützlich
Abflachen ist ein großartiger Anwendungsfall für reduce – es gibt derzeit auch einen Vorschlag, eine
flat()-Methode hinzuzufügen. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatIch muss zugeben, die einzige Frage, die ich mir stelle, ist warum! Die ‚for loop‘ ist ziemlich offensichtlich und sagt alles.
Ich schätze, das ist eigentlich nur eine Lambda-Funktion, also warum sie nicht so nennen. Selbst dann ist ein Lambda für so etwas übertrieben.
Vielleicht bin nur ich, aber Sprachen (nicht nur JS) werden komplizierter, ohne einen wirklich guten Grund oder einen echten Nutzen!
Aber ich schätze, das hält die Werkzeuge und Pädagogen sowie die Bug-Jäger beschäftigt, da die neuen Werkzeuge/Funktionen von den Benutzern verhunzt werden.
oder vielleicht bin ich einfach ein altmodischer Reaktionär :)
Nichtsdestotrotz gute Erklärung – danke
Sie müssen sicherlich keine funktionale Programmierung betreiben, wenn sie Ihnen nicht zusagt.
Sie verweisen auf andere Artikel, die ich gelesen habe, aber Sie haben es buchstäblich auf Begriffe reduziert, die ich verstehen kann.