Das große JavaScript-Array-Tutorial

14.08.2024
Von 
Matthew Tyson ist Java-Entwickler und schreibt unter anderem für unsere US-Schwesterpublikation Infoworld.com.
Lesen Sie, wie Sie in JavaScript integrierte Funktionen nutzen, um Arrays elegant(er) zu händeln.
Ordnung ist das halbe Leben - auch, wenn's um JavaScript-Arrays geht.
Ordnung ist das halbe Leben - auch, wenn's um JavaScript-Arrays geht.
Foto: anatoliy gleb | shutterstock.com

JavaScript-Arrays bieten eine sehr vielseitige und flexible Möglichkeit, Collections mit Techniken der funktionalen Programmierung zu modellieren. In diesem Tutorial erfahren Sie alles was Sie wissen müssen, um JavaScript-Arrays im funktionalen Stil zu erstellen. Unter anderem mit integrierten Tools wie:

  • forEach(),

  • map() oder

  • reduce().

Traditionelle JavaScript-Arrays

Arrays in JavaScript können heterogene Typen enthalten, ihre Größe spontan ändern und es ist jederzeit möglich, ihnen Elemente hinzuzufügen oder diese zu entfernen. Traditionelle Methoden wie Slice, Splice und Push/Pop operieren dazu auf dem Array selbst - und modifizieren die Collection auf "destruktive" Art und Weise:

// Create an array with heterogeneous types:

let myArray = [10, "hello", true, { name: "Alice" }];

// Add to an array and change size on the fly:

myArray.push(42);

// Extract elements without modifying the original array:

let extracted = myArray.slice(1, 3);

JavaScript-Arrays im funktionalen Stil

Obwohl JavaScript-Arrays bereits von Haus aus sehr leistungsfähig sind, sorgen die Paradigmen der funktionalen Programmierung dafür, dass Array-Code übersichtlicher und wartbarer wird. Beim Functional Programming werden Funktionen im allgemeinen als Operatoren verwendet, die an Arrays übergeben werden können. Das ermöglicht, mit Arrays ähnlich wie ein Tonkopf mit einem Tape zu verfahren - statt auf die traditionellen, imperativen Loops zu setzen, die im Detail beschreiben, was sich abspielen soll. Im Folgenden einige Beispiele für die Arbeit mit Arrays unter Berücksichtigung funktionaler Paradigmen.

forEach()

Eine gängige Alternative zum traditionellen for-Loop bietet Array.forEach(). Damit können Sie eine Funktion übergeben, die iterativ beliebige Operationen auf den Array-Elementen durchführt.

myArray.forEach((element) => {

console.log("my element is: " + element);

})

In diesem Beispiel wird einfach jedes Element auf der Konsole ausgegeben. Das for-Loop-Äquivalent würde folgendermaßen aussehen:

for (let i = 0; i < myArray.length; i++) {

console.log("my element is: " + myArray[i]);

}

Wie Sie sehen gibt es in der funktionalen Version im Vergleich weniger bewegliche Teile. Insbesondere entfällt der Iterator (i), der eine Fremdvariable darstellt, die dazu dient, die mechanische Logik auszudrücken. Das soll nicht bedeuten, dass for-Loops keine Daseinsberechtigung haben (dazu später mehr). Sie sind in manchen Fällen das richtige Werkzeug, allerdings ist der forEach()-Ansatz im Regelfall sauberer.

Unveränderlichkeit respektive Immutability ist fester Bestandteil der Functional-Programming-Philosophie. Statt Variablen zu verändern, werden diese beim funktionalen Ansatz durch eine Pipeline (eine oder mehrere Funktionen) geleitet und in eine neue Variable transformiert. Dabei bleibt die ursprüngliche Variable unverändert erhalten. Auf diese Weise wird auch forEach oft verwendet. Das Argument lässt sich jedoch auch "destruktiv" einsetzen - wie im folgenden Beispiel:

const numbers = [1, 2, 3, 4, 5];

numbers.forEach(function(number, index) {

if (number % 2 === 0) { // Check for even numbers

numbers.splice(index, 1); // Remove even numbers from the array

}

});

Dieses Beispiel ist weit entfernt davon, funktionale Programmierung "in Reinkultur" zu verkörpern, aber es beinhaltet entscheidende Charakteristiken des Ansatzes - beispielsweise First-Order Functions. Eine "Funktion erster Ordnung" wird wie jede andere Referenz verwendet. In diesem Fall, indem sie als Argument übergeben wird. Kurzum: Funktionen können als "tragbare Funktionalitätsbündel" genutzt werden, die weitergereicht werden, um bestimmte Aufgaben auf vorhersehbare Art und Weise zu erledigen.

Davon abgesehen gibt es immer noch viele Fälle, in denen der gute alte for-Loop den besten Ansatz darstellt. Zum Beispiel:

  • bei der Iteration durch eine andere Zahl als 1,

  • bei einer rückwärtsgerichteten Iteration und

  • wenn es darum geht, komplexer Szenarien zu handhaben, die mehrere Iteratoren erfordern.

Array.map()

Funktionen, die weder "destruktiv" sind, noch andere "Nebeneffekte" aufweisen, werden als "Pure Functions" bezeichnet. Die Funktion Array.map() ist speziell für diese Zwecke konzipiert. Sie arbeitet nicht mit dem Array selbst, sondern führt den Funktionsoperator aus und gibt das Ergebnis als neues Array zurück:

const bands = [

{ name: "Led Zeppelin", year: 1968 },

{ name: "Pink Floyd", year: 1965 },

{ name: "Queen", year: 1970 },

{ name: "The Clash", year: 1976 },

{ name: "The Ramones", year: 1974 },

{ name: "R.E.M.", year: 1980 },

];

const bandNames = bands.map(band => {

return band.name;

});

// bandNames is an array that has just the string band names

Bei Array.map() handelt es sich um einen leistungsfähigen Mechanismus, um Arrays zu transformieren. Insbesondere vermeidet es die Komplexität, die entsteht, wenn das ursprüngliche Array geändert wird und sich dann herausstellt, dass Abhängigkeiten zu anderen Code-Bestandteilen bestehen.

Andererseits sollten Sie bedenken, dass Array.map() immer eine Kopie erstellt, was Auswirkungen auf die Performance hat. Es ist deshalb nicht empfehlenswert, diese Funktion für sehr große Arrays zu verwenden.

Array.filter()

Array.map() gibt ein Array mit der gleichen Länge wie die Quelle aus. Wenn die Funktion nichts zurückgibt, wird das ausgegebene Array an dieser Stelle als undefiniert bezeichnet. Um ein Array mit einer anderen Länge zu erzeugen, können Sie Array.filter() verwenden. In diesem Fall wird das Element aus dem Ziel-Array entfernt, wenn das Funktionsargument nichts zurückgibt:

const bands = [

{ name: "Led Zeppelin", year: 1968 },

{ name: "Pink Floyd", year: 1965 },

{ name: "Queen", year: 1970 },

{ name: "The Clash", year: 1976 },

{ name: "The Ramones", year: 1974 },

{ name: "R.E.M.", year: 1980 },

];

const seventiesBands = bands.filter(band => {

if (band.year >= 1970 && band.year < 1980) {

return band;

}

});

// seventiesBands is an array holding only those bands satisfying the condition (band.year >= 1970 && band.year < 1980)

In diesem Beispiel nehmen wir ein Array mit Objekten, die Rockbands und das Jahr ihrer Gründung enthalten. Anschließend verwenden wir bands.filter(), um eine Funktion bereitzustellen, die ein neues Array liefert, das nur die Bands aus den 1970er Jahren enthält.

Array.reduce()

Wenn Sie ein ganzes Array in einen einzigen Wert umwandeln möchten, können Sie dazu Array.reduce verwenden:

// same band array as source

const earliestBand = bands.reduce((earliestSoFar, band) => {

return band.year < earliestSoFar.year ? band : earliestSoFar;

}, { year: Infinity }); // Start with a band in the infinitely distant future

console.log(earliestBand.name); // outputs "Pink Floyd"

Die an reduce() übergebene Funktion hat zwei Argumente: den "Akkumulator" und das aktuelle Element. Der Akkumulator ist das, was schließlich zurückgegeben wird und hält seinen Zustand über jede Iteration hinweg. Das ermöglicht Ihnen, alles in einem einzigen Output zu "sammeln".

Die reduce-Funktion ist ein äußerst praktisches Tool. Um beim Beispiel mit den Rockbands zu bleiben - wenn Sie einen String mit allen Bandnamen erstellen wollen, würden Sie wie folgt vorgehen:

const allBandNames = bands.reduce((accumulator, band) => {

return accumulator + band.name + ", ";

}, ""); // Initial value is an empty string

Funktionen verknüpfen

Die in JavaScript integrierten Funktionen, die Sie bisher kennengelernt haben, sind grundlegend für die funktionale (und reaktive) Programmierarbeit. Im Folgenden widmen wir uns dem Thema, Funktionen miteinander zu verknüpfen, um eine gewünschte Funktionalität zu erreichen. Zwei der grundlegendsten und wichtigsten Verknüpfungsfunktionen sind:

  • compose() und

  • chain().

Sie sind in vielen funktionalen Programmier- und Hilfsbibliotheken enthalten, lassen sich aber auch leicht implementieren. Das folgende Beispiel gibt Ihnen einen klaren Einblick in ihre Funktionsweise:

const compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x);

const chain = (...fns) => (xs) => xs.reduce((acc, x) => acc.concat(fns.reduceRight((v, f) => f(v), x)), []);

compose() kombiniert mehrere Funktionen, so dass der Output jeder Funktion in die nächste eingespeist wird - und zwar von rechts nach links (basierend auf der Reihenfolge, in der sie der Funktion übergeben wurde). chain() bewerkstelligt dasselbe, aber von links nach rechts. Bei reduceRight() handelt es sich um das Spiegelbild von reduce(). Hiermit können akkumulieren, indem Sie die funktionalen Argumente rückwärts durchgehen.

Die Funktionen compose() und chain() sind nicht Array-spezifisch, können aber in diesem Zusammenhang verwendet werden. Im Folgenden ein einfaches Beispiel, wie compose() mit einem Array genutzt werden kann:

const numbers = [1, 4, 2, 8, 5, 7];

// Define reusable higher-order functions:

const findEvenNumbers = arr => arr.filter(n => n % 2 === 0);

const doubleNumbers = arr => arr.map(n => n * 2);

const sortNumbers = arr => arr.sort((a, b) => a - b);

// Compose functions to create complex transformations:

const processNumbers = compose(sortNumbers, doubleNumbers, findEvenNumbers);

const processedNumbers = processNumbers(numbers);

console.log(processedNumbers); // Output: [4, 8, 16]

Funktionen zu arrangieren, ist sowohl für die funktionale als auch die reaktive Programmierung von zentraler Bedeutung. Sie ermöglicht es, Funktionen wiederzuverwenden und zu neuen Funktionen zu kombinieren. Im Wesentlichen können Sie zusammengesetzte Funktionen definieren, die sich aus den Fähigkeiten anderer, gezielterer Funktionen zusammensetzen. Das ist konzeptionell vergleichbar mit der Art und Weise, wie ein objektorientierter Programmierer denkt, wenn es darum geht, Applikationen aus Objekten zusammenzustellen. (fm)