Generics in JavaScript: warum sie nützlich sind und wie man sie verwendet
In der modernen Entwicklung mit JavaScript und Node.js begegnet man immer häufiger dem Konzept Generics (generische oder verallgemeinerte Typen). Obwohl natives JavaScript keine strenge Typisierung unterstützt, ermöglichen Werkzeuge wie TypeScript und JSDoc die Implementierung leistungsstarker Mechanismen der generischen Programmierung. In diesem Artikel werden wir uns ansehen, warum Generics benötigt werden, wie sie im Kontext von Node.js funktionieren und wie man sie richtig einsetzt.
Was sind Generics und warum werden sie benötigt?
Generics sind ein Mechanismus, der es ermöglicht, Komponenten (Funktionen, Klassen, Interfaces) zu erstellen, die mit verschiedenen Datentypen arbeiten, dabei aber eine strenge Typisierung beibehalten. Anstatt dieselbe Funktion für Zahlen, Zeichenketten und Arrays zu schreiben, schreibt man einen einzigen generischen Code.
Die Hauptvorteile von Generics:
- Typsicherheit — Typfehler werden bereits zur Kompilierungs-/Überprüfungszeit abgefangen, nicht erst zur Laufzeit.
- Wiederverwendbarkeit von Code — eine Funktion arbeitet mit beliebigen Datentypen.
- Lesbarkeit — der Code wird selbstdokumentierend.
- Integration mit der IDE — Autovervollständigung und Typ-Hinweise funktionieren korrekt.
In Node.js sind Generics besonders nützlich bei der Arbeit mit Datenbanken, API-Anfragen, Datensammlungen und Hilfsfunktionen.
Generics in TypeScript (der primäre Weg)
TypeScript ist eine Erweiterung von JavaScript, die statische Typisierung hinzufügt. In TypeScript sind Generics am umfassendsten implementiert. Betrachten wir die grundlegende Syntax.
Einfache generische Funktion
// Ohne Generics — man muss any verwendenfunction identity(arg: any): any { return arg;}
// Mit Generics — der Typ bleibt erhaltenfunction identity<T>(arg: T): T { return arg;}
// Verwendungconst num = identity<number>(42); // Typ numberconst str = identity<string>("hallo"); // Typ stringDie spitzen Klammern <T> deklarieren den Typparameter. T ist ein Platzhaltername, kann beliebig sein (häufig verwendet man T, U, V).
Generics mit Arrays
Einer der häufigsten Fälle ist die Arbeit mit Arrays:
// Funktion, die das erste Element eines Arrays zurückgibtfunction getFirst<T>(arr: T[]): T | undefined { return arr[0];}
const firstNumber = getFirst([1, 2, 3]); // numberconst firstString = getFirst(["a", "b", "c"]); // stringMehrere Typen
Man kann mehrere Typparameter verwenden:
function pair<T, U>(first: T, second: U): [T, U] { return [first, second];}
const result = pair(42, "antwort"); // Typ [number, string]Einschränkungen (Constraints) bei Generics
Manchmal muss man einschränken, welche Typen verwendet werden dürfen. Dazu verwendet man Constraints mit dem Schlüsselwort extends.
// Funktion, die nur mit Objekten arbeitet, die eine length-Eigenschaft habenfunction getLength<T extends { length: number }>(arg: T): number { return arg.length;}
getLength("hallo"); // 5 — Zeichenkette hat lengthgetLength([1, 2, 3]); // 3 — Array hat length// getLength(42); // Fehler! number hat kein lengthDies ist besonders nützlich in Node.js bei der Arbeit mit Buffern, Zeichenketten und Arrays — also allem, was eine length-Eigenschaft besitzt.
Generics in Klassen und Interfaces
Generics funktionieren nicht nur mit Funktionen, sondern auch mit Klassen und Interfaces hervorragend.
Generische Klasse
Beispiel eines einfachen Stacks in Node.js:
class Stack<T> { private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop
```