JavaScript-Funktionssyntax
Lerne die "new Function"-Syntax in JavaScript: Funktionen aus Strings zur Laufzeit erstellen, Scope-Regeln, Sicherheit gegenüber eval und Anwendungsfälle.
Meistens erstellt man eine Funktion mit einer Deklaration, einem Ausdruck oder einer Pfeilfunktion. JavaScript bietet jedoch noch eine weitere Möglichkeit: den new Function-Konstruktor, der eine Funktion aus Strings zur Laufzeit aufbaut. Diese Seite konzentriert sich auf diese Syntax — ihre genaue Form, die Scope-Regeln, die die meisten Entwickler überraschen, den Vergleich mit eval sowie die wenigen Fälle, in denen sie das richtige Werkzeug ist.
Die new Function-Syntax
Die new Function-Syntax ermöglicht es, eine Funktion zu erstellen, deren Parameter und Rumpf als Strings angegeben werden. Da der Rumpf bis zur Ausführung nur Text ist, kann man eine Funktion zusammenstellen, deren Code beim Schreiben des Programms noch nicht bekannt ist — sondern erst in dem Moment, in dem es ausgeführt wird.
Die allgemeine Form lautet:
let func = new Function([arg1, arg2, ...argN], functionBody);Jedes Argument ist ein string. Die ersten Argumente benennen die Parameter; das letzte Argument ist immer der Funktionsrumpf.
Alle Parameternamen können auch in einem einzigen kommaseparierten string angegeben werden, was gleichwertig ist:
Das Schlüsselwort new ist hier optional — Function('a', 'b', 'return a + b') liefert dasselbe Ergebnis — aber die Schreibweise new Function(...) ist die übliche und klarere Form.
Warum es existiert
Das Besondere an new Function im Vergleich zu einer normalen Deklaration ist, dass der Rumpf aus einem String stammt. Dieser String kann von überall kommen: einer Server-Antwort, einem Template, einer Benutzerkonfiguration oder Text, der zur Laufzeit zusammengesetzt wird. Die Syntax existiert daher genau für jene Fälle, in denen der auszuführende Code beim Schreiben des Programms noch nicht existiert.
Scope: Die große Falle
Dies ist das Detail, über das alle stolpern. Eine mit new Function erstellte Funktion erfasst den Scope, in dem sie erstellt wurde, nicht. Anders als eine normale Closure ist ihre äußere lexikalische Umgebung der globale Scope, nicht der lokale.
function makeAdder() {
let outer = 100;
// This function tries to read `outer`...
return new Function('x', 'return x + outer');
}
const add = makeAdder();
add(5); // ReferenceError: outer is not definedEine reguläre Funktion, die genauso geschrieben wurde, würde outer problemlos über eine Closure einschließen. Die new Function-Version kann das nicht — sie sieht nur ihre eigenen Parameter und den globalen Scope:
Dies ist beabsichtigt. Könnte new Function auf lokale Variablen zugreifen, würden Minifier (die outer in a, secret in b usw. umbenennen) jeden Code zerstören, der diese Namen als Strings referenziert. Indem der Zugriff auf den globalen Scope beschränkt wird, bleibt die Minifizierung sicher. Die praktische Schlussfolgerung: Alles, was eine dynamische Funktion benötigt, muss über ihre Argumente übergeben werden — man darf nie erwarten, dass sie umgebende Variablen liest.
Eigenschaften einer dynamischen Funktion
Eine so erstellte Funktion ist in jeder anderen Hinsicht ein normales Funktionsobjekt, mit einer Besonderheit — ihr name ist immer "anonymous":
Dieser quasi-leere Name ist ein Grund, warum dynamische Funktionen in Stack-Traces schwerer zu lesen sind — siehe den Debugging-Hinweis weiter unten.
new Function vs. eval
Sowohl new Function als auch eval wandeln Strings in ausführbaren Code um, verhalten sich aber sehr unterschiedlich:
eval(str)führtstrim aktuellen Scope aus und kann dadurch nahe gelegene lokale Variablen lesen und sogar verändern. Diese enge Kopplung erschwert die Optimierung und erleichtert den Missbrauch.new Functionist vom lokalen Scope isoliert (wie oben gezeigt) und liefert eine wiederverwendbare Funktion zurück statt einer einmaligen Auswertung.
Für den seltenen Fall, dass es wirklich notwendig ist, Code aus einem String auszuführen, ist new Function die sicherere der beiden Optionen, da ihr Wirkungsbereich auf den globalen Scope und die expliziten Parameter beschränkt ist.
Praktische Anwendungen und Beispiele
Nachfolgend ein vollständiges, ausführbares Beispiel, das im Browser bearbeitet und ausgeführt werden kann.
Weiterführende Einblicke in die dynamische Funktionserstellung
Die dynamische Funktionserstellung in JavaScript, ermöglicht durch die new Function-Syntax, ist eine leistungsstarke Technik, mit der Entwickler Funktionen aus Code-Strings zur Laufzeit erstellen können. Diese Fähigkeit ist besonders nützlich in Szenarien, in denen der auszuführende Code nicht statisch oder zum Voraus bekannt ist — etwa in Anwendungen, die ein hohes Maß an Flexibilität erfordern, oder in Situationen, in denen Skripte dynamisch generiert oder verändert werden. In diesem Abschnitt werden die Mechanismen, Vorteile und Überlegungen zur dynamischen Funktionserstellung vertieft und praktische Beispiele gegeben, um das Potenzial zu veranschaulichen.
Mechanismen der dynamischen Funktionserstellung
Die new Function-Syntax erstellt eine neue Funktionsinstanz. Die Argumente des new Function-Konstruktors sind Strings, die die Argumente der Funktion darstellen, gefolgt von einem String, der den Funktionsrumpf repräsentiert.
Dies entspricht funktional der Deklaration einer Funktion auf herkömmliche Weise, mit dem wesentlichen Unterschied, dass der Code der Funktion dynamisch zur Laufzeit zusammengesetzt werden kann.
Vorteile der dynamischen Funktionserstellung
- Flexibilität und Anpassbarkeit: Die dynamische Funktionserstellung ermöglicht ein hohes Maß an Anpassung, da Funktionen basierend auf Benutzereingaben, Konfigurationseinstellungen oder anderen Laufzeitdaten generiert werden können.
- Scripting und Templating: Sie ist besonders nützlich bei der Implementierung benutzerdefinierter Scripting-Lösungen oder Template-Engines, bei denen die Template-Logik zur Laufzeit ausgewertet werden muss.
- Isolation und Sicherheit: Bei sorgfältigem Einsatz kann Code in einer kontrollierten Umgebung ausgeführt werden, wobei der dynamisch ausgeführte Code potenziell vom Hauptanwendungskontext isoliert wird.
Überlegungen und Best Practices
Obwohl die dynamische Funktionserstellung leistungsstark ist, bringt sie einige Überlegungen mit sich:
- Sicherheit: Das primäre Anliegen ist die Sicherheit. Da der Funktionscode aus Strings zusammengesetzt wird, besteht das Risiko, bösartigen Code auszuführen, wenn die Eingabe nicht ordnungsgemäß bereinigt wird. Eingaben, die zur Generierung von Funktionscode verwendet werden, müssen stets validiert und bereinigt werden.
- Performance: Dynamisch erstellte Funktionen können weniger performant sein als statisch deklarierte, da die JavaScript-Engine den Funktionsrumpf-String jedes Mal parsen muss, wenn eine neue Funktion erstellt wird. Diese Funktion sollte mit Bedacht verwendet werden, insbesondere in performance-kritischen Pfaden.
- Debugging: Das Debuggen dynamisch generierter Funktionen kann schwieriger sein, da der Code bis zur Laufzeit nicht existiert. Aussagekräftige Namen für dynamisch erstellte Funktionen können dieses Problem abmildern.
- Einschränkung des lexikalischen Scopes: Mit
new Functionerstellte Funktionen erfassen nicht den lokalen Scope, in dem sie definiert sind. Sie haben nur Zugriff auf globale Variablen und ihre eigenen Parameter. Dies kann zuReferenceErrors führen, wenn erwartet wird, dass sie auf äußere Variablen zugreifen. (Siehe den Scope-Abschnitt oben; Daten müssen über Argumente übergeben werden.)
Erweitertes Beispiel: Eine einfache Template-Engine
Um den praktischen Einsatz der dynamischen Funktionserstellung zu veranschaulichen, betrachten wir die Implementierung einer einfachen Template-Engine. Diese Engine ersetzt Platzhalter in einem Template-String durch Werte aus einem Datenobjekt — und die Daten werden dabei als Argument übergeben, um die Scope-Einschränkung zu umgehen.
Hinweis: Die Sequenz \${ maskiert die Template-Literal-Syntax. Dadurch wird verhindert, dass der ${expr}-Platzhalter sofort ausgewertet wird, und er wird stattdessen als literaler String an den generierten Funktionsrumpf übergeben.
Dieses Beispiel demonstriert nicht nur die Flexibilität der dynamischen Funktionserstellung, sondern verdeutlicht auch die Bedeutung einer sorgfältigen Konstruktion und Bereinigung der Eingabe, um Sicherheitsrisiken zu vermeiden.
Zusammenfassung
Der new Function-Konstruktor erstellt eine Funktion aus Strings zur Laufzeit:
let func = new Function([arg1, ..., argN], functionBody);Wichtige Punkte zum Merken:
- Das letzte Argument ist immer der Funktionsrumpf; die früheren benennen die Parameter.
- Der äußere Scope einer dynamisch erstellten Funktion ist global, nicht der Ort ihrer Erstellung — sie kann keine lokalen Variablen einschließen, daher müssen Daten über Argumente übergeben werden.
- Sie ist im Allgemeinen sicherer als
eval(das im aktuellen Scope ausgeführt wird), aber beide werten Strings aus — daher sollte ihnen nur vertrauenswürdige, bereinigte Eingabe übergeben werden. - Verwende sie für wirklich dynamischen Code — Template-Engines, isolierte Ausdrucksauswertungen, zur Laufzeit generierte Handler — und bevorzuge überall sonst gewöhnliche Funktionen oder Pfeilfunktionen.
Für weiterführende Informationen zu verwandten Themen, siehe JavaScript Closures, Variable Scope und Function Expressions.