Eine Funktion ist ein parametrischer Codeblock, der einmal definiert und später mehrfach aufgerufen wird. In JavaScript wird eine Funktion aus vielen Komponenten zusammengesetzt und beeinflusst:
- JavaScript-Code, der den Funktionskörper bildet
- Die Liste der Parameter
- Die Variablen, auf die aus dem lexikalischen Bereich zugegriffen werden kann
- Der Rückgabewert
- Der Kontext
this
beim Aufruf der Funktion - Benannte oder anonyme Funktion
- Die Variable, die das Funktionsobjekt enthält
-
arguments
Objekt (oder fehlend bei einer Pfeilfunktion)
In diesem Beitrag lernen Sie sechs Ansätze zur Deklaration von JavaScript-Funktionen kennen: Die Syntax, Beispiele und häufige Fallstricke. Außerdem werden Sie verstehen, wann Sie einen bestimmten Funktionstyp unter bestimmten Umständen verwenden sollten.
1. Funktionsdeklaration
Eine Funktionsdeklaration besteht aus dem Schlüsselwort
function
, gefolgt von einem obligatorischen Funktionsnamen, einer Liste von Parametern in einem Klammerpaar(para1, ..., paramN)
und einem Paar geschweifter Klammern{...}
, das den Body-Code abgrenzt.
Ein Beispiel für eine Funktionsdeklaration:
// function declarationfunction isEven(num) { return num % 2 === 0;}isEven(24); // => trueisEven(11); // => false
function isEven(num) {...}
ist eine Funktionsdeklaration, die eine isEven
Funktion definiert, die bestimmt, ob eine Zahl gerade ist.
Die Funktionsdeklaration erzeugt eine Variable im aktuellen Gültigkeitsbereich mit dem Bezeichner gleich dem Funktionsnamen. Diese Variable enthält das Funktionsobjekt.
Die Funktionsvariable wird an den Anfang des aktuellen Gültigkeitsbereichs gehievt, was bedeutet, dass die Funktion vor der Deklaration aufgerufen werden kann (siehe dieses Kapitel für weitere Details).
Die erzeugte Funktion ist benannt, was bedeutet, dass die name
-Eigenschaft des Funktionsobjekts ihren Namen enthält. Das ist nützlich, wenn man den Aufrufstapel betrachtet: beim Debuggen oder beim Lesen von Fehlermeldungen.
Schauen wir uns diese Eigenschaften in einem Beispiel an:
// Hoisted variableconsole.log(hello('Aliens')); // => 'Hello Aliens!'// Named functionconsole.log(hello.name) // => 'hello'// Variable holds the function objectconsole.log(typeof hello); // => 'function'function hello(name) { return `Hello ${name}!`;}
Die Funktionsdeklaration function hello(name) {...}
erzeugt eine Variable hello
, die an den Anfang des aktuellen Bereichs gehievt wird. hello
Variable enthält das Funktionsobjekt und hello.name
enthält den Funktionsnamen: 'hello'
.
1.1 Eine reguläre Funktion
Die Funktionsdeklaration passt für Fälle, in denen eine reguläre Funktion benötigt wird. Regelmäßig bedeutet, dass Sie die Funktion einmal deklarieren und sie später an vielen verschiedenen Stellen aufrufen. Das ist das Grundszenario:
function sum(a, b) { return a + b;}sum(5, 6); // => 11().reduce(sum) // => 10
Da die Funktionsdeklaration neben regulären Funktionsaufrufen auch eine Variable im aktuellen Gültigkeitsbereich anlegt, ist sie für Rekursionen oder das Ablösen von Event-Listenern nützlich. Im Gegensatz zu Funktionsausdrücken oder Pfeilfunktionen, die keine Bindung an die Funktionsvariable über ihren Namen erzeugen.
Um beispielsweise den Faktor rekursiv zu berechnen, müssen Sie die Funktion innerhalb von:
function factorial(n) { if (n === 0) { return 1; } return n * factorial(n - 1);}factorial(4); // => 24
Innerhalb von factorial()
wird ein rekursiver Aufruf über die Variable, die die Funktion enthält, gemacht: factorial(n - 1)
.
Es ist möglich, einen Funktionsausdruck zu verwenden und ihn einer regulären Variablen zuzuweisen, z. B. const factorial = function(n) {...}
. Aber die Funktionsdeklaration function factorial(n)
ist kompakt (keine Notwendigkeit für const
und =
).
Eine wichtige Eigenschaft der Funktionsdeklaration ist ihr Aufhebungsmechanismus. Er erlaubt es, die Funktion vor der Deklaration im gleichen Gültigkeitsbereich zu verwenden.
Hoisting ist in einigen Situationen nützlich. Zum Beispiel, wenn man am Anfang eines Skripts sehen möchte, wie die Funktion aufgerufen wird, ohne die Funktionsimplementierung zu lesen. Die Funktionsimplementierung kann sich weiter unten in der Datei befinden, so dass Sie vielleicht nicht einmal dorthin blättern müssen.
Weitere Details zur Funktionsdeklaration lesen Sie hier.
1.2 Unterschied zum Funktionsausdruck
Funktionsdeklaration und Funktionsausdruck sind leicht zu verwechseln. Sie sehen sich sehr ähnlich, erzeugen aber Funktionen mit unterschiedlichen Eigenschaften.
Eine leicht zu merkende Regel: Die Funktionsdeklaration in einer Anweisung beginnt immer mit dem Schlüsselwort function
. Ansonsten ist es ein Funktionsausdruck (siehe 2.).
Das folgende Beispiel ist eine Funktionsdeklaration, bei der die Anweisung mit dem Schlüsselwort function
beginnt:
// Function declaration: starts with "function"function isNil(value) { return value == null;}
Bei Funktionsausdrücken beginnt die JavaScript-Anweisung nicht mit dem Schlüsselwort function
(es befindet sich irgendwo in der Mitte des Anweisungscodes):
// Function expression: starts with "const"const isTruthy = function(value) { return !!value;};// Function expression: an argument for .filter()const numbers = ().filter(function(item) { return typeof item === 'number';});// Function expression (IIFE): starts with "("(function messageFunction(message) { return message + ' World!';})('Hello');
1.3 Funktionsdeklaration in Conditionals
Einige JavaScript-Umgebungen können einen Referenzfehler auslösen, wenn eine Funktion aufgerufen wird, deren Deklaration innerhalb von Blöcken {...}
von if
for
oder while
Anweisungen erscheint.
Lassen Sie uns den Strict-Modus aktivieren und sehen, was passiert, wenn eine Funktion in einem Conditional deklariert wird:
(function() { 'use strict'; if (true) { function ok() { return 'true ok'; } } else { function ok() { return 'false ok'; } } console.log(typeof ok === 'undefined'); // => true console.log(ok()); // Throws "ReferenceError: ok is not defined"})();
Beim Aufruf von ok()
wirft JavaScript ReferenceError: ok is not defined
, weil die Funktionsdeklaration innerhalb eines Conditional-Blocks steht.
Die Funktionsdeklaration in Conditionals ist im nicht-strikten Modus erlaubt, was die Sache noch verwirrender macht.
Als allgemeine Regel für diese Situationen gilt: Wenn eine Funktion durch Bedingungen erzeugt werden soll – verwenden Sie einen Funktionsausdruck. Schauen wir uns an, wie das möglich ist:
(function() { 'use strict'; let ok; if (true) { ok = function() { return 'true ok'; }; } else { ok = function() { return 'false ok'; }; } console.log(typeof ok === 'function'); // => true console.log(ok()); // => 'true ok'})();
Da die Funktion ein reguläres Objekt ist, weisen Sie sie je nach Bedingung einer Variablen zu. Der Aufruf von ok()
funktioniert problemlos und ohne Fehler.
2. Funktionsausdruck
Ein Funktionsausdruck wird durch ein
function
Schlüsselwort, gefolgt von einem optionalen Funktionsnamen, bestimmt, einer Liste von Parametern in einem Klammerpaar(para1, ..., paramN)
und einem Paar geschweifter Klammern{ ... }
, das den Body-Code abgrenzt.
Ein paar Beispiele für den Funktionsausdruck:
const count = function(array) { // Function expression return array.length;}const methods = { numbers: , sum: function() { // Function expression return this.numbers.reduce(function(acc, num) { // func. expression return acc + num; }); }}count(); // => 3methods.sum(); // => 14
Der Funktionsausdruck erzeugt ein Funktionsobjekt, das in verschiedenen Situationen verwendet werden kann:
- Zuweisung an eine Variable als Objekt
count = function(...) {...}
- Erzeugen einer Methode auf ein Objekt
sum: function() {...}
- Verwenden Sie die Funktion als Rückruf
.reduce(function(...) {...})
Der Funktionsausdruck ist das Arbeitspferd in JavaScript. In der Regel haben Sie es mit dieser Art der Funktionsdeklaration zu tun, neben der Pfeilfunktion (wenn Sie eine kurze Syntax und lexikalischen Kontext bevorzugen).
2.1. Benannter Funktionsausdruck
Eine Funktion ist anonym, wenn sie keinen Namen hat (name
Eigenschaft ist eine leere Zeichenkette ''
):
( function(variable) {return typeof variable; }).name; // => ''
Dies ist eine anonyme Funktion, deren Name eine leere Zeichenkette ist.
Manchmal kann der Funktionsname hergeleitet werden. Zum Beispiel, wenn die anonyme einer Variablen zugewiesen ist:
const myFunctionVar = function(variable) { return typeof variable; };myFunctionVar.name; // => 'myFunctionVar'
Der Name der anonymen Funktion ist 'myFunctionVar'
, weil myFunctionVar
der Variablenname verwendet wird, um den Funktionsnamen abzuleiten.
Wenn der Ausdruck den angegebenen Namen hat, handelt es sich um einen benannten Funktionsausdruck. Er hat einige zusätzliche Eigenschaften im Vergleich zum einfachen Funktionsausdruck:
- Eine benannte Funktion wird erzeugt, d. h.
name
Eigenschaft enthält den Funktionsnamen - Innerhalb des Funktionskörpers enthält eine Variable mit dem gleichen Namen das Funktionsobjekt
Lassen Sie uns das obige Beispiel verwenden, aber einen Namen im Funktionsausdruck setzen:
const getType = function funName(variable) { console.log(typeof funName === 'function'); // => true return typeof variable;}console.log(getType(3)); // => 'number'console.log(getType.name); // => 'funName'console.log(typeof funName); // => 'undefined'
function funName(variable) {...}
ist ein benannter Funktionsausdruck. Die Variable funName
ist innerhalb des Funktionsumfangs zugreifbar, aber nicht außerhalb. In jedem Fall enthält die Eigenschaft name
des Funktionsobjekts den Namen: funName
.
2.2 Favorisierter benannter Funktionsausdruck
Wenn ein Funktionsausdruck const fun = function() {}
einer Variablen zugewiesen wird, schließen einige Engines aus dieser Variable auf den Funktionsnamen. Es kann jedoch vorkommen, dass Callbacks als anonyme Funktionsausdrücke übergeben werden, ohne dass sie in Variablen gespeichert werden: So kann die Engine den Namen nicht ermitteln.
Es ist sinnvoll, benannte Funktionen zu bevorzugen und anonyme zu vermeiden, um Vorteile zu erzielen wie:
- Die Fehlermeldungen und Aufrufstapel zeigen detailliertere Informationen, wenn die Funktionsnamen verwendet werden
- Komfortableres Debugging durch Reduzierung der anonymen Stack-Namen
- Der Funktionsname sagt, was die Funktion tut
- Sie können auf die Funktion innerhalb ihres Gültigkeitsbereichs zugreifen, um rekursive Aufrufe durchzuführen oder Ereignis-Listener zu lösen
3. Shorthand-Methodendefinition
Shorthand-Methodendefinition kann in einer Methodendeklaration auf Objektliterale und ES2015-Klassen verwendet werden. Sie können sie mit einem Funktionsnamen definieren, gefolgt von einer Liste von Parametern in einem Klammerpaar
(para1, ..., paramN)
und einem Paar geschweifter Klammern{ ... }
, das die Body-Anweisungen abgrenzt.
Das folgende Beispiel verwendet eine Shorthand-Methodendefinition in einem Objektliteral:
const collection = { items: , add(...items) { this.items.push(...items); }, get(index) { return this.items; }};collection.add('C', 'Java', 'PHP');collection.get(1) // => 'Java'
add()
und get()
Methoden im collection
Objekt werden per Kurzmethoden-Definition definiert. Diese Methoden werden wie gewohnt aufgerufen: collection.add(...)
und collection.get(...)
.
Der kurze Ansatz der Methodendefinition hat mehrere Vorteile gegenüber der traditionellen Eigenschaftsdefinition mit einem Namen, Doppelpunkt :
und einem Funktionsausdruck add: function(...) {...}
:
- Eine kürzere Syntax ist einfacher zu verstehen
- Die kurze Methodendefinition erzeugt eine benannte Funktion, im Gegensatz zu einem Funktionsausdruck. Sie ist nützlich für das Debugging.
Die class
-Syntax erfordert Methodendeklarationen in Kurzform:
class Star { constructor(name) { this.name = name; } getMessage(message) { return this.name + message; }}const sun = new Star('Sun');sun.getMessage(' is shining') // => 'Sun is shining'
3.1 Berechnete Eigenschaftsnamen und Methoden
ECMAScript 2015 fügt ein nettes Feature hinzu: berechnete Eigenschaftsnamen in Objektliteralen und Klassen.
Die berechneten Eigenschaften verwenden eine etwas andere Syntax () {...}
, sodass die Methodendefinition so aussieht:
const addMethod = 'add', getMethod = 'get';const collection = { items: , (...items) { this.items.push(...items); }, (index) { return this.items; }};collection('C', 'Java', 'PHP');collection(1) // => 'Java'
(...) {...}
und (...) {...}
sind kurz gefasste Methodendeklarationen mit berechneten Eigenschaftsnamen.
4. Pfeilfunktion
Eine Pfeilfunktion wird mit einem Klammerpaar definiert, das die Liste der Parameter enthält
(param1, param2, ..., paramN)
, gefolgt von einem fetten Pfeil=>
und einem Paar geschweifter Klammern{...}
, das die Body-Anweisungen abgrenzt.
Wenn die Pfeilfunktion nur einen Parameter hat, kann das Klammerpaar weggelassen werden. Wenn sie eine einzelne Anweisung enthält, können die geschweiften Klammern ebenfalls weggelassen werden.
Lassen Sie uns die grundlegende Verwendung der Pfeilfunktion sehen:
const absValue = (number) => { if (number < 0) { return -number; } return number;}absValue(-10); // => 10absValue(5); // => 5
absValue
ist eine Pfeilfunktion, die den Absolutwert einer Zahl berechnet.
Die mit einem fetten Pfeil deklarierte Funktion hat folgende Eigenschaften:
- Die Pfeilfunktion erzeugt ihren Ausführungskontext nicht, sondern übernimmt ihn lexikalisch (im Gegensatz zu Funktionsausdruck oder Funktionsdeklaration, die je nach Aufruf einen eigenen
this
erzeugen) - Die Pfeilfunktion ist anonym. Die Engine kann jedoch aus der Variable, die die Funktion enthält, auf ihren Namen schließen.
-
arguments
Objekt ist in der Pfeilfunktion nicht verfügbar (im Gegensatz zu anderen Deklarationstypen, diearguments
Objekt zur Verfügung stellen). Es steht Ihnen aber frei, Restparameter(...params)
zu verwenden.
4.1 Kontexttransparenz
this
Schlüsselwort ist ein verwirrender Aspekt von JavaScript (eine ausführliche Erklärung zu this
finden Sie in diesem Artikel).
Da Funktionen einen eigenen Ausführungskontext erzeugen, ist es oft schwierig, den this
-Wert zu erkennen.
ECMAScript 2015 verbessert die Verwendung von this
durch die Einführung der Pfeilfunktion, die den Kontext lexikalisch übernimmt (oder einfach this
aus dem unmittelbaren äußeren Bereich verwendet). Das ist nett, weil Sie nicht .bind(this)
verwenden oder den Kontext var self = this
speichern müssen, wenn eine Funktion den umschließenden Kontext benötigt.
Lassen Sie uns sehen, wie this
von der äußeren Funktion geerbt wird:
class Numbers { constructor(array) { this.array = array; } addNumber(number) { if (number !== undefined) { this.array.push(number); } return (number) => { console.log(this === numbersObject); // => true this.array.push(number); }; }}const numbersObject = new Numbers();const addMethod = numbersObject.addNumber();addMethod(1);addMethod(5);console.log(numbersObject.array); // =>
Numbers
Die Klasse hält ein Array von Zahlen und stellt eine Methode addNumber()
zum Einfügen neuer Zahlen bereit.
Wenn addNumber()
ohne Argumente aufgerufen wird, wird eine Closure zurückgegeben, die das Einfügen von Zahlen erlaubt. Diese Closure ist eine Pfeilfunktion, die this
als numbersObject
-Instanz hat, da der Kontext lexikalisch von der addNumbers()
-Methode übernommen wird.
Ohne die Pfeilfunktion müssen Sie den Kontext manuell fixieren. Das bedeutet, dass Sie Workarounds wie die .bind()
-Methode verwenden müssen:
//... return function(number) { console.log(this === numbersObject); // => true this.array.push(number); }.bind(this);//...
oder den Kontext in einer separaten Variablen var self = this
speichern:
//... const self = this; return function(number) { console.log(self === numbersObject); // => true self.array.push(number); };//...
Die Kontexttransparenz kann verwendet werden, wenn Sie this
so belassen wollen, wie sie ist, nämlich aus dem umschließenden Kontext übernommen.
4.2 Kurze Callbacks
Bei der Erstellung einer Pfeilfunktion sind die Klammerpaare und geschweiften Klammern für einen einzelnen Parameter und eine einzelne Body-Anweisung optional. Das hilft bei der Erstellung sehr kurzer Callback-Funktionen.
Lassen Sie uns eine Funktion erstellen, die herausfindet, ob ein Array 0
enthält:
const numbers = ;numbers.some(item => item === 0); // => true
item => item === 0
ist eine Pfeilfunktion, die einfach aussieht.
Beachten Sie, dass verschachtelte kurze Pfeilfunktionen schwer zu lesen sind. Die bequemste Art, die kürzeste Pfeilfunktionsform zu verwenden, ist ein einzelner Callback (ohne Verschachtelung).
Wenn nötig, verwenden Sie die erweiterte Syntax von Pfeilfunktionen, wenn Sie verschachtelte Pfeilfunktionen schreiben. Sie ist einfach leichter zu lesen.
5. Generatorfunktion
Die Generatorfunktion in JavaScript gibt ein Generator-Objekt zurück. Ihre Syntax ähnelt dem Funktionsausdruck, der Funktionsdeklaration oder der Methodendeklaration, nur dass sie ein Sternzeichen *
benötigt.
Die Generatorfunktion kann in den folgenden Formen deklariert werden:
a. Funktionsdeklarationsform function* <name>()
:
function* indexGenerator(){ var index = 0; while(true) { yield index++; }}const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
b. Funktionsausdruck-Formular function* ()
:
const indexGenerator = function* () { let index = 0; while(true) { yield index++; }};const g = indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
c. Kurzform der Methodendefinition *<name>()
:
const obj = { *indexGenerator() { var index = 0; while(true) { yield index++; } }}const g = obj.indexGenerator();console.log(g.next().value); // => 0console.log(g.next().value); // => 1
In allen 3 Fällen liefert die Generatorfunktion das Generatorobjekt g
. Später wird g
verwendet, um eine Reihe von inkrementierten Zahlen zu erzeugen.
6. Noch etwas: neue Funktion
In JavaScript sind Funktionen Objekte erster Klasse – eine Funktion ist ein reguläres Objekt vom Typ function
.
Die oben beschriebenen Wege der Deklaration erzeugen den gleichen Objekttyp Funktion. Sehen wir uns ein Beispiel an:
function sum1(a, b) { return a + b;}const sum2 = function(a, b) { return a + b;}const sum3 = (a, b) => a + b;console.log(typeof sum1 === 'function'); // => trueconsole.log(typeof sum2 === 'function'); // => trueconsole.log(typeof sum3 === 'function'); // => true
Der Funktionsobjekttyp hat einen Konstruktor: Function
.
Wenn Function
als Konstruktor new Function(arg1, arg2, ..., argN, bodyString)
aufgerufen wird, wird eine neue Funktion erzeugt. Die Argumente arg1, args2, ..., argN
, die dem Konstruktor übergeben werden, werden zu den Parameternamen für die neue Funktion und das letzte Argument bodyString
wird als Code für den Funktionsrumpf verwendet.
Lassen Sie uns eine Funktion erstellen, die zwei Zahlen summiert:
const numberA = 'numberA', numberB = 'numberB';const sumFunction = new Function(numberA, numberB, 'return numberA + numberB');sumFunction(10, 15) // => 25
sumFunction
erstellt mit Function
Konstruktor Aufruf hat die Parameter numberA
und numberB
und den Body return numberA + numberB
.
Die so erzeugten Funktionen haben keinen Zugriff auf den aktuellen Gültigkeitsbereich, so dass keine Closures erzeugt werden können. Sie werden immer im globalen Bereich erstellt.
Eine mögliche Anwendung von new Function
ist ein besserer Weg, um auf das globale Objekt in einem Browser oder NodeJS-Skript zuzugreifen:
(function() { 'use strict'; const global = new Function('return this')(); console.log(global === window); // => true console.log(this === window); // => false})();
Erinnern Sie sich, dass Funktionen fast nie mit new Function()
deklariert werden sollten. Da der Funktionskörper zur Laufzeit ausgewertet wird, bringt dieser Ansatz viele eval()
Nutzungsprobleme mit sich: Sicherheitsrisiken, erschwerte Fehlersuche, keine Möglichkeit, Engine-Optimierungen anzuwenden, keine Auto-Vervollständigung im Editor.
7. Welcher Weg ist am Ende besser?
Es gibt keinen Gewinner oder Verlierer. Die Entscheidung, welche Deklarationsart man wählt, hängt von der jeweiligen Situation ab.
Es gibt jedoch einige Regeln, die Sie in häufigen Situationen befolgen können.
Wenn die Funktion this
von der umschließenden Funktion verwendet, ist die Pfeilfunktion eine gute Lösung. Wenn die Callback-Funktion eine kurze Anweisung hat, ist die Pfeilfunktion ebenfalls eine gute Option, weil sie kurzen und leichten Code erzeugt.
Für eine kürzere Syntax bei der Deklaration von Methoden auf Objektliteralen ist die Shorthand-Methoden-Deklaration zu bevorzugen.
new Function
Die Methode zur Deklaration von Funktionen sollte normalerweise nicht verwendet werden. Vor allem, weil es potenzielle Sicherheitsrisiken eröffnet, keine Code-Autovervollständigung in Editoren erlaubt und die Engine-Optimierungen verliert.