(...) aber ich verstehe nicht, wie ich diesen Code lesen muss. (...)
Der Quellcode wird von oben nach unten gelesen. Bei Zuweisungen wird erst der rechtsseitige Ausdruck ausgewertet und Funktionen werden erst ausgeführt, sobald sie explizit aufgerufen werden.
Auf was verweist aber beispielsweise const name = replacements[0] oder oder const age = replacements[1].
Die Variable replacements wird als Parameter im Funktionskopf deklariert:
function tagFunction(strings, ...replacements) {
Hierbei wird die rest parameter-Syntax angewandt. Die drei Punkte vor dem Parameternamen deuten an, dass bei Aufruf der Funktion beliebig viele Argumente an der Stelle übergeben werden können.
Das heißt, Aufrufe wie diese wären valid:
tagFunction("Max", 1);
tagFunction("Max", 1, 2);
tagFunction("Max", "a", "b", "c");
Diese Argumente werden anschließend in ein Array gesteckt, auf welches replacements zeigt.
Folgendermaßen hättest du, würdest du die obigen Aufrufe nutzen, folgende Ergebnisse für name und age:
tagFunction("Max", 1):
name: 1
age: undefined
tagFunction("Max", 1, 2):
name: 1
age: 2
tagFunction("Max", "a", "b", "c"):
name: "a"
age: "b"
An dieser Stelle solltest du merken, dass du die Funktion nicht so aufrufst, wie es eigentlich angedacht war. Die Werte für Name und Alter sollten eigentlich als separate Argumente übergeben werden und damit es mit dem resultierenden Text auch passt, müsste für das erste Argument ebenfalls ein Array erstellt werden.
const name = "Max Mustermann";
const age = 98;
const text = [ "Mein Name ist ", ", ich bin ", " Jahre alt" ];
const message = tagFunction(text, name, age);
Bezüglich Funktionsaufrufen würde ich übrigens dazu raten, stets auch die Klammern für die Argumentenliste mit anzugeben. So wird beim Lesen schneller deutlich, dass es sich gerade um einen Funktionsaufruf handelt.
Und mal ganz nebenbei: Wie würdet ihr die praktische Relevanz der Tag-Funktion einschätzen?
Aktuell ist diese Funktion nur für einen bestimmten Anwendungsfall beschränkt und kann daher wohl eher als Demonstration / Beispiel für die rest parameter-Syntax betrachtet werden.
In der Praxis sollte sie auf jeden Fall einen passenden Namen erhalten, der auch verrät, was sie tut. Das Gleiche gilt für den ersten Parameter.
Die rest parameter-Syntax an dieser Stelle zu verwenden, halte ich zudem für einen schlechten Stil. Wenn die Funktion zwei Werte erwartet, sollten auch explizit zwei Parameter in der Parameterliste aufgelistet werden. Das hilft nicht zuletzt auch dem Programmierer, der die Funktion irgendwann einmal nutzen möchte: Er sieht, welche Argumente die Funktion erwartet und kann anschließend selbst entscheiden, inwiefern er sie bedient.
Bei der aktuellen Implementation muss man stattdessen erst die Funktionsimplementation studieren, bevor man weiß, was in die jeweiligen Argumente darf. Das ist zeitaufwendig und unintuitiv.
Relevanz könnte die Funktion erlangen, wenn man sie für einen allgemeineren Anwendungsfall umschreibt.
function format(template, ...args) {
return template.replace(/{(\d+)}/g, (match, number) => {
return typeof args[number] === "undefined" ? match : args[number];
});
}
console.log(format("Hello, my name is {0} and I'm {1} years old.", "John Doe", 56));
Das erste Argument ist ein Text mit Platzhaltermarken. Diese werden gegen die konkreten Werte ausgetauscht, die man als weitere Argumente übergibt. Dabei gilt: Das erste Zusatzargument wird gegen {0} getauscht, das zweite Zusatzargument gegen {1}, usw..
Vorteilhaft gegenüber einer String-Interpolation ist, dass so View (die Ausgabe) und Model (die konkreten Daten) voneinander getrennt werden. Der Template-String braucht bei mehrfacher Verwendung (mit unterschiedlichen Platzhalterwerten) nur einmal definiert werden.
const template = "Hello, my name is {0} and I'm {1} years old.";
console.log(format(template, "John Doe", 56));
console.log(format(template, "Max Mustermann", 23));