ES6-Pfeilfunktionen: Fette und prägnante Syntax in JavaScript

In diesem Artikel erfahren Sie alles über die Syntax von JavaScript-Pfeilfunktionen – einschließlich einiger Stolpersteine, die Sie bei der Verwendung von Pfeilfunktionen in Ihrem Code beachten müssen. Anhand zahlreicher Beispiele wird ihre Funktionsweise veranschaulicht.

Pfeilfunktionen wurden mit der Veröffentlichung von ECMAScript 2015, auch bekannt als ES6, in JavaScript eingeführt. Ihre prägnante Syntax und die Art und Weise, wie sie mit dem this-Schlüsselwort umgehen, gehören zu den Hauptmerkmalen, die zum beachtlichen Erfolg von Pfeilfunktionen unter Entwicklern beigetragen haben.

Die Umwandlung einer Pre-ES6-Funktion in eine Pfeilfunktion

Sie können Funktionen als eine Art Rezept betrachten, in dem Sie nützliche Anweisungen speichern, um etwas zu erreichen, das Sie in Ihrem Programm benötigen, wie das Ausführen einer Aktion oder die Rückgabe eines Wertes. Durch den Aufruf Ihrer Funktion führen Sie die in Ihrem Rezept enthaltenen Schritte aus, und Sie können dies jedes Mal tun, wenn Sie die Funktion aufrufen, ohne das Rezept immer wieder neu schreiben zu müssen.

Hier ist eine Standardmethode, um eine Funktion zu deklarieren und sie dann in JavaScript aufzurufen:

// function declarationfunction sayHiStranger() { return 'Hi, stranger!'}// call the functionsayHiStranger()

Sie können die gleiche Funktion auch als Funktionsausdruck schreiben, etwa so:

const sayHiStranger = function () { return 'Hi, stranger!'}

Pfeilfunktionen sind immer Ausdrücke. So könnte man die obige Funktion unter Verwendung der fetten Pfeilschreibweise umschreiben:

const sayHiStranger = () => 'Hi, stranger'

Die Vorteile sind:

  • nur eine Zeile Code
  • kein function Schlüsselwort
  • kein return Schlüsselwort
  • keine geschweiften Klammern {}

In JavaScript sind Funktionen „Bürger erster Klasse“. Das heißt, Sie können Funktionen in Variablen speichern, sie an andere Funktionen als Argumente übergeben und sie von anderen Funktionen als Werte zurückgeben. Das alles kann man mit Pfeilfunktionen machen.

Lassen Sie uns die verschiedenen Möglichkeiten durchgehen, wie Sie Pfeilfunktionen schreiben können.

Der No Parens Syntax

Im obigen Beispiel hat die Funktion keine Parameter. In diesem Fall müssen Sie vor dem fetten Pfeilsymbol (=>) einen Satz leerer Klammern () hinzufügen. Das Gleiche gilt, wenn Sie mehr als einen Parameter haben:

const getNetflixSeries = (seriesName, releaseDate) => `The ${seriesName} series was released in ${releaseDate}`// call the functionconsole.log(getNetflixSeries('Bridgerton', '2020') )// output: The Bridgerton series was released in 2020

Bei nur einem Parameter können Sie jedoch die Klammern weglassen (Sie müssen nicht, aber Sie können):

const favoriteSeries = seriesName => seriesName === "Bridgerton" ? "Let's watch it" : "Let's go out"// call the functionconsole.log(favoriteSeries("Bridgerton"))// output: "Let's watch it"

Sei aber vorsichtig. Wenn Sie zum Beispiel einen Standardparameter verwenden wollen, müssen Sie ihn in Klammern setzen:

// with parentheses: correctconst bestNetflixSeries = (seriesName = "Bridgerton") => `${seriesName} is the best`// outputs: "Bridgerton is the best"console.log(bestNetflixSeries())// no parentheses: errorconst bestNetflixSeries = seriesName = "Bridgerton" => `${seriesName} is the best`// Uncaught SyntaxError: invalid arrow-function arguments (parentheses around the arrow-function may help)

Und nur weil Sie es können, heißt das nicht, dass Sie es sollten. Kyle Simpson (von You Don’t Know JS) hat seine Gedanken über das Weglassen von Klammern in diesem Flussdiagramm zusammengefasst.

Implizite Rückgabe

Wenn Sie nur einen Ausdruck in Ihrem Funktionskörper haben, können Sie alles in einer Zeile halten, die geschweiften Klammern entfernen und das Schlüsselwort return weglassen. In den obigen Beispielen haben Sie gerade gesehen, wie diese raffinierten Einzeiler funktionieren. Hier ist noch ein weiteres Beispiel, nur um sicherzugehen. Die Funktion orderByLikes() tut das, was auf der Verpackung steht: Sie gibt ein Array von Netflix-Serienobjekten zurück, geordnet nach der höchsten Anzahl von Likes:

// using the JS sort() function to sort the titles in descending order // according to the number of likes (more likes at the top, fewer at the bottomconst orderByLikes = netflixSeries.sort( (a, b) => b.likes - a.likes )// call the function // output:the titles and the n. of likes in descending orderconsole.log(orderByLikes)

Das ist cool, aber achten Sie auf die Lesbarkeit Ihres Codes – vor allem, wenn Sie eine Reihe von Pfeilfunktionen mit Einzeilern und der Syntax ohne Klammern aneinanderreihen, wie in diesem Beispiel:

const greeter = greeting => name => `${greeting}, ${name}!`

Was ist hier los? Versuchen Sie, die reguläre Funktionssyntax zu verwenden:

function greeter(greeting) { return function(name) { return `${greeting}, ${name}!` }} 

Nun können Sie schnell erkennen, dass die äußere Funktion greeter einen Parameter greeting hat und eine anonyme Funktion zurückgibt. Diese innere Funktion hat ihrerseits einen Parameter namens name und gibt eine Zeichenkette zurück, die den Wert von greeting und name verwendet. So können Sie die Funktion aufrufen:

const myGreet = greeter('Good morning')console.log( myGreet('Mary') ) // output: "Good morning, Mary!" 

Achten Sie auf diese impliziten Rückgabefehler

Wenn Ihre Pfeilfunktion mehr als eine Anweisung enthält, müssen Sie sie alle in geschweifte Klammern einschließen und das Schlüsselwort return verwenden. Im folgenden Code erstellt die Funktion ein Objekt, das den Titel und die Zusammenfassung einiger Netflix-Serien enthält (Netflix-Bewertungen stammen von der Website Rotten Tomatoes) :

const seriesList = netflixSeries.map( series => { const container = {} container.title = series.name container.summary = series.summary // explicit return return container} )

Die Pfeilfunktion innerhalb der Funktion .map() entwickelt sich über eine Reihe von Anweisungen, an deren Ende sie ein Objekt zurückgibt. Dies macht die Verwendung geschweifter Klammern um den Körper der Funktion unvermeidlich. Da Sie geschweifte Klammern verwenden, ist auch eine implizite Rückgabe nicht möglich. Sie müssen das Schlüsselwort return verwenden.

Wenn Ihre Pfeilfunktion ein Objektliteral zurückgibt, indem Sie die implizite Rückgabe verwenden, müssen Sie das Objekt in runde Klammern einschließen. Wenn Sie dies nicht tun, führt dies zu einem Fehler, da die JS-Engine die geschweiften Klammern des Objektliteral fälschlicherweise als geschweifte Klammern der Funktion interpretiert. Und wie Sie gerade oben bemerkt haben, können Sie das Schlüsselwort return nicht weglassen, wenn Sie geschweifte Klammern in einer Pfeilfunktion verwenden.

Diese Syntax wird in der kürzeren Version des vorherigen Codes demonstriert:

// Uncaught SyntaxError: unexpected token: ':'const seriesList = netflixSeries.map(series => { title: series.name });// Works fineconst seriesList = netflixSeries.map(series => ({ title: series.name }));

Sie können Pfeilfunktionen nicht benennen

Funktionen, die keinen Namensbezeichner zwischen dem Schlüsselwort function und der Parameterliste haben, werden anonyme Funktionen genannt. So sieht ein regulärer anonymer Funktionsausdruck aus:

const anonymous = function() { return 'You can\'t identify me!' }

Pfeilfunktionen sind alle anonyme Funktionen:

const anonymousArrowFunc = () => 'You can\'t identify me!' 

Ab ES6 können Variablen und Methoden den Namen einer anonymen Funktion aus ihrer syntaktischen Position ableiten, indem sie die Eigenschaft name verwenden. Dadurch ist es möglich, die Funktion zu identifizieren, wenn man ihren Wert untersucht oder einen Fehler meldet.

Überprüfen Sie dies mit anonymousArrowFunc:

console.log(anonymousArrowFunc.name)// output: "anonymousArrowFunc"

Aber beachten Sie, dass diese abgeleitete name-Eigenschaft nur existiert, wenn die anonyme Funktion einer Variablen zugewiesen ist, wie in den obigen Beispielen. Wenn Sie eine anonyme Funktion z.B. als Rückruf verwenden, verlieren Sie diese nützliche Eigenschaft. Dies wird in der folgenden Demo deutlich, in der die anonyme Funktion innerhalb der .setInterval()-Methode die name-Eigenschaft nicht nutzen kann:

let counter = 5let countDown = setInterval(() => { console.log(counter) counter-- if (counter === 0) { console.log("I have no name!!") clearInterval(countDown) }}, 1000)

Und das ist noch nicht alles. Diese abgeleitete name-Eigenschaft funktioniert immer noch nicht als richtiger Bezeichner, mit dem man auf die Funktion selbst verweisen kann – zum Beispiel für Rekursion, Entbindungsereignisse, usw.

Die inhärente Anonymität von Pfeilfunktionen hat Kyle Simpson dazu veranlasst, seine Ansicht über Pfeilfunktionen wie folgt zu formulieren:

Da ich nicht glaube, dass anonyme Funktionen eine gute Idee sind, wenn man sie häufig in seinen Programmen verwendet, bin ich kein Fan der Verwendung der => Pfeilfunktionsform. – You Don’t Know JS

Wie Pfeilfunktionen mit dem Schlüsselwort this umgehen

Das Wichtigste bei Pfeilfunktionen ist die Art und Weise, wie sie mit dem Schlüsselwort this umgehen. Insbesondere wird das Schlüsselwort this innerhalb einer Pfeilfunktion nicht umgebogen.

Um zu veranschaulichen, was das bedeutet, sehen Sie sich die folgende Demo an:

See the Pen
JS this in arrow functions by SitePoint (@SitePoint)
on CodePen.

Hier ist ein Button. Ein Klick auf die Schaltfläche löst einen Rückwärtszähler von 5 bis 1 aus. Die Zahlen werden auf der Schaltfläche selbst angezeigt:

<button class="start-btn">Start Counter</button>...const startBtn = document.querySelector(".start-btn");startBtn.addEventListener('click', function() { this.classList.add('counting') let counter = 5; const timer = setInterval(() => { this.textContent = counter counter -- if(counter < 0) { this.textContent = 'THE END!' this.classList.remove('counting') clearInterval(timer) } }, 1000) })

Beachten Sie, dass der Event-Handler innerhalb der .addEventListener()-Methode ein regulärer anonymer Funktionsausdruck und keine Pfeilfunktion ist. Warum eigentlich? Wenn Sie this innerhalb der Funktion protokollieren, werden Sie sehen, dass sie auf das Schaltflächenelement verweist, an das der Listener angehängt wurde, was genau das ist, was erwartet wird und was notwendig ist, damit das Programm wie geplant funktioniert:

startBtn.addEventListener('click', function() { console.log(this) ...})

So sieht es in der Konsole der Firefox-Entwicklertools aus:

Versuchen Sie jedoch, die reguläre Funktion durch eine Pfeilfunktion zu ersetzen, etwa so:

startBtn.addEventListener('click', () => { console.log(this) ...})

Nun verweist this nicht mehr auf die Schaltfläche. Stattdessen verweist sie auf das Objekt Window:

Das bedeutet, dass Ihr Code nicht funktionieren wird, wenn Sie this verwenden möchten, um der Schaltfläche eine Klasse hinzuzufügen, nachdem sie angeklickt wurde, zum Beispiel:

// change button's border's appearancethis.classList.add('counting')

Hier ist die Fehlermeldung in der Konsole:

Dies geschieht, weil bei der Verwendung einer Pfeilfunktion der Wert des Schlüsselworts this nicht zurückgesetzt wird, sondern vom übergeordneten Bereich geerbt wird (dies wird lexikalisches Scoping genannt). In diesem speziellen Fall wird die betreffende Pfeilfunktion als Argument an die Methode startBtn.addEventListener() übergeben, die sich im globalen Bereich befindet. Folglich ist das this innerhalb des Pfeilfunktions-Handlers auch an den globalen Bereich gebunden – d.h. an das Window-Objekt.

Wenn Sie also wollen, dass this auf die Start-Schaltfläche im Programm verweist, ist es richtig, eine reguläre Funktion zu verwenden, keine Pfeilfunktion.

Das nächste, was in der obigen Demo auffällt, ist der Code innerhalb der Methode .setInterval(). Auch hier finden Sie eine anonyme Funktion, aber dieses Mal ist es eine Pfeilfunktion. Und warum?

Beachte, welchen Wert this hätte, wenn du eine normale Funktion verwenden würdest:

const timer = setInterval(function() { console.log(this) ...}, 1000)

Wäre es das Element button? Ganz und gar nicht. Es wäre das Objekt Window!

In der Tat hat sich der Kontext geändert, denn this befindet sich jetzt innerhalb einer ungebundenen oder globalen Funktion, die als Argument an .setInterval() übergeben wird. Daher hat sich auch der Wert des Schlüsselworts this geändert, da es nun an den globalen Bereich gebunden ist. Ein üblicher Hack in dieser Situation ist das Einfügen einer weiteren Variablen, um den Wert des Schlüsselworts this zu speichern, so dass es sich weiterhin auf das erwartete Element bezieht – in diesem Fall auf das Element button:

const that = thisconst timer = setInterval(function() { console.log(that) ...}, 1000)

Sie können auch .bind() verwenden, um das Problem zu lösen:

const timer = setInterval(function() { console.log(this) ...}.bind(this), 1000)

Mit Pfeilfunktionen verschwindet das Problem ganz und gar. So sieht der Wert von this aus, wenn Sie eine Pfeilfunktion verwenden:

const timer = setInterval( () => { console.log(this) ...}, 1000)

Diesmal protokolliert die Konsole die Schaltfläche, was genau das ist, was wir brauchen. Tatsächlich wird das Programm den Text der Schaltfläche ändern, also braucht es this, um auf das button-Element zu verweisen:

const timer = setInterval( () => { console.log(this) // the button's text displays the timer value this.textContent = counter}, 1000)

Pfeilfunktionen haben keinen eigenen this-Kontext. Sie erben den Wert von this vom übergeordneten Element, und genau wegen dieser Eigenschaft sind sie in Situationen wie der obigen eine gute Wahl.

Pfeilfunktionen sind nicht immer das richtige Werkzeug für den Job

Pfeilfunktionen sind nicht nur eine schicke neue Art, Funktionen in JavaScript zu schreiben. Sie haben ihre eigenen Macken und Einschränkungen, was bedeutet, dass es Fälle gibt, in denen man eine Pfeilfunktion nicht verwenden sollte. Der Click-Handler in der vorherigen Demo ist ein Beispiel dafür, aber nicht das einzige. Schauen wir uns ein paar weitere an.

Pfeilfunktionen als Objektmethoden

Pfeilfunktionen funktionieren nicht gut als Methoden für Objekte. Hier ist ein Beispiel. Betrachten Sie dieses netflixSeries Objekt, das einige Eigenschaften und einige Methoden hat. Der Aufruf von console.log(netflixSeries.getLikes()) sollte eine Meldung mit der aktuellen Anzahl der Likes ausgeben, und der Aufruf von console.log(netflixSeries.addLike()) sollte die Anzahl der Likes um eins erhöhen und dann den neuen Wert mit einer Dankesmeldung auf der Konsole ausgeben:

const netflixSeries = { title: 'After Life', firstRealease: 2019, likes: 5, getLikes: () => `${this.title} has ${this.likes} likes`, addLike: () => { this.likes++ return `Thank you for liking ${this.title}, which now has ${this.likes} likes` } }

Anstatt dessen gibt der Aufruf der Methode .getLikes() zurück „undefined has NaN likes“, und der Aufruf der Methode .addLike() gibt zurück „Thank you for liking undefined, which now has NaN likes“. this.title und this.likes können also nicht auf die Objekteigenschaften title bzw. likes verweisen.

Auch hier liegt das Problem im lexikalischen Scoping der Pfeilfunktionen. Das this innerhalb der Methode des Objekts verweist auf den Bereich des Elternteils, der in diesem Fall das Window-Objekt ist, nicht das Elternteil selbst – also nicht das netflixSeries-Objekt.

Die Lösung ist natürlich, eine reguläre Funktion zu verwenden:

const netflixSeries = { title: 'After Life', firstRealease: 2019, likes: 5, getLikes() { return `${this.title} has ${this.likes} likes` }, addLike() { this.likes++ return `Thank you for liking ${this.title}, which now has ${this.likes} likes` } }// call the methods console.log(netflixSeries.getLikes())console.log(netflixSeries.addLike())// output: After Life has 5 likesThank you for liking After Life, which now has 6 likes

Pfeilfunktionen mit Bibliotheken von Drittanbietern

Ein weiteres Problem, dessen man sich bewusst sein sollte, ist, dass Bibliotheken von Drittanbietern oft Methodenaufrufe binden, so dass der this-Wert auf etwas Nützliches zeigt.

In einem jQuery-Ereignishandler gibt this beispielsweise Zugriff auf das DOM-Element, an das der Handler gebunden wurde:

$('body').on('click', function() { console.log(this)})// <body>

Wenn wir jedoch eine Pfeilfunktion verwenden – die, wie wir gesehen haben, keinen eigenen this-Kontext hat – erhalten wir unerwartete Ergebnisse:

$('body').on('click', () =>{ console.log(this)})// Window

Hier ist ein weiteres Beispiel mit Vue.js:

new Vue({ el: app, data: { message: 'Hello, World!' }, created: function() { console.log(this.message); }})// Hello, World!

Innerhalb des created-Hooks ist this an die Vue-Instanz gebunden, sodass die Nachricht „Hello, World!“ angezeigt wird.

Wenn wir jedoch eine Pfeilfunktion verwenden, zeigt this auf den übergeordneten Bereich, der keine message-Eigenschaft hat:

new Vue({ el: app, data: { message: 'Hello, World!' }, created: function() { console.log(this.message); }})// undefined

Pfeilfunktionen haben keine Argumente Objekt

Gelegentlich müssen Sie vielleicht eine Funktion mit einer unbestimmten Anzahl von Parametern erstellen. Nehmen wir zum Beispiel an, Sie möchten eine Funktion erstellen, die Ihre Netflix-Lieblingsserien nach Vorlieben sortiert auflistet. Sie wissen jedoch noch nicht, wie viele Serien Sie einbeziehen wollen. JavaScript stellt das arguments-Objekt zur Verfügung, ein Array-ähnliches Objekt (allerdings kein vollwertiges Array), das die Werte speichert, die beim Aufruf der Funktion übergeben werden.

Versuchen Sie, diese Funktionalität mit einer Pfeilfunktion zu implementieren:

const listYourFavNetflixSeries = () => { // we need to turn the arguments into a real array // so we can use .map() const favSeries = Array.from(arguments) return favSeries.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) console.log(arguments)}console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life')) 

Wenn Sie die Funktion aufrufen, erhalten Sie die folgende Fehlermeldung: Uncaught ReferenceError: arguments is not defined. Das bedeutet, dass das Objekt arguments innerhalb von Pfeilfunktionen nicht verfügbar ist. Wenn Sie die Pfeilfunktion durch eine reguläre Funktion ersetzen, ist das kein Problem:

const listYourFavNetflixSeries = function() { const favSeries = Array.from(arguments) return favSeries.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) console.log(arguments) }console.log(listYourFavNetflixSeries('Bridgerton', 'Ozark', 'After Life'))// output: 

Wenn Sie also das arguments-Objekt benötigen, können Sie keine Pfeilfunktionen verwenden.

Was aber, wenn Sie wirklich eine Pfeilfunktion verwenden möchten, um die gleiche Funktionalität zu replizieren? Eine Möglichkeit ist die Verwendung von ES6-Restparametern (...). So könnten Sie Ihre Funktion umschreiben:

const listYourFavNetflixSeries = (...seriesList) => { return seriesList.map( (series, i) => { return `${series} is my #${i +1} favorite Netflix series` } ) }

Schlussfolgerung

Durch die Verwendung von Pfeilfunktionen können Sie prägnante Einzeiler mit impliziter Rückgabe schreiben und endlich die alten Hacks vergessen, um die Bindung des Schlüsselworts this in JavaScript zu lösen. Pfeilfunktionen funktionieren auch hervorragend mit Array-Methoden wie .map(), .sort(), .forEach(), .filter() und .reduce(). Aber denken Sie daran, Pfeilfunktionen sind kein Ersatz für reguläre JS-Funktionen, und sie können nicht für alles verwendet werden.

Wenn Sie Fragen zu Pfeilfunktionen haben oder Hilfe brauchen, um sie richtig einzusetzen, empfehle ich Ihnen einen Besuch in den freundlichen SitePoint-Foren, wo viele sachkundige Programmierer bereit sind, Ihnen zu helfen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.