502 Stimmen

Wann sollte ich Pfeilfunktionen in ECMAScript 6 verwenden?

Mit () => {} und function () {} erhalten wir zwei sehr ähnliche Möglichkeiten, Funktionen in ES6 zu schreiben. In anderen Sprachen unterscheiden sich Lambda-Funktionen oft dadurch, dass sie anonym sind, aber in ECMAScript kann jede Funktion anonym sein. Jeder der beiden Typen hat einzigartige Anwendungsbereiche (zum Beispiel, wenn this entweder explizit gebunden werden muss oder explizit nicht gebunden werden muss). Zwischen diesen Bereichen gibt es eine große Anzahl von Fällen, in denen beide Notationen funktionieren werden.

Pfeilfunktionen in ES6 haben mindestens zwei Einschränkungen:

  • Funktionieren nicht mit new und können nicht verwendet werden, wenn prototype erstellt wird
  • Fixiertes this an den Bereich bei der Initialisierung gebunden

Abgesehen von diesen beiden Einschränkungen könnten Pfeilfunktionen theoretisch normale Funktionen fast überall ersetzen. Was ist der richtige Ansatz für ihre praktische Verwendung? Sollten Pfeilfunktionen z.B. verwendet werden:

  • "überall dort, wo sie funktionieren", d.h. überall dort, wo eine Funktion nicht agnostisch bezüglich der this-Variable sein muss und wir kein Objekt erstellen
  • nur "überall dort, wo sie benötigt werden", z.B. bei Event Listeners, Timeouts, die an einen bestimmten Bereich gebunden werden müssen
  • mit 'kurzen' Funktionen, aber nicht mit 'langen' Funktionen
  • nur mit Funktionen, die keine weitere Pfeilfunktion enthalten

Ich suche nach einer Leitlinie zur Auswahl der geeigneten Funktionsnotation in der zukünftigen Version von ECMAScript. Die Leitlinie muss klar sein, damit sie Entwicklern in einem Team beigebracht werden kann, und konsistent sein, so dass sie kein ständiges Refactoring von einer Funktionsnotation zur anderen erfordert.

Die Frage richtet sich an Personen, die über Codestil im Kontext des kommenden ECMAScript 6 (Harmony) nachgedacht haben und bereits mit der Sprache gearbeitet haben.

380voto

lyschoening Punkte 17420

Einige Zeit zuvor hat unser Team den gesamten Code (eine mittelgroße AngularJS-App) in JavaScript umgestellt, der mit Traceur Babel kompiliert wurde. Jetzt verwende ich die folgende Faustregel für Funktionen in ES6 und höher:

  • Verwenden Sie function im globalen Bereich und für Eigenschaften von Object.prototype.
  • Verwenden Sie class für Objektkonstruktoren.
  • Verwenden Sie => überall sonst.

Warum werden Pfeilfunktionen fast überall verwendet?

  1. Bereichssicherheit: Wenn Pfeilfunktionen konsequent verwendet werden, ist garantiert, dass alles dasselbe thisObject wie die Wurzel verwendet. Wenn sogar ein einzelner Standardfunktionsrückruf mit einer Menge von Pfeilfunktionen gemischt wird, besteht die Gefahr, dass der Bereich durcheinander gerät.
  2. Kompaktheit: Pfeilfunktionen sind einfacher zu lesen und zu schreiben. (Dies mag subjektiv erscheinen, daher werde ich einige Beispiele weiter unten geben.)
  3. Klarheit: Wenn fast alles eine Pfeilfunktion ist, fällt eine normale function sofort auf, um den Bereich zu definieren. Ein Entwickler kann immer die nächsthöhere function-Anweisung nachschlagen, um zu sehen, was das thisObject ist.

Warum sollten immer reguläre Funktionen im globalen Bereich oder Modulbereich verwendet werden?

  1. Um eine Funktion anzugeben, die nicht auf das thisObject zugreifen sollte.
  2. Das window-Objekt (globaler Bereich) sollte explizit angesprochen werden.
  3. Viele Object.prototype-Definitionen befinden sich im globalen Bereich (denken Sie an String.prototype.truncate usw.) und diese müssen im Allgemeinen sowieso vom Typ function sein. Durch die konsistente Verwendung von function im globalen Bereich können Fehler vermieden werden.
  4. Viele Funktionen im globalen Bereich sind Objektkonstruktoren für altmodische Klassendefinitionen.
  5. Funktionen können benannt werden1. Dies hat zwei Vorteile: (1) Es ist weniger umständlich, function foo(){} zu schreiben als const foo = () => {} – insbesondere außerhalb anderer Funktionsaufrufe. (2) Der Funktionsname wird in Stapelrückverfolgungen angezeigt. Auch wenn es mühsam wäre, jeden internen Rückruf zu benennen, ist es wahrscheinlich eine gute Idee, alle öffentlichen Funktionen zu benennen.
  6. Funktionsdeklarationen werden hoisted, (was bedeutet, dass sie aufgerufen werden können, bevor sie deklariert werden), was eine nützliche Eigenschaft in einer statischen Dienstprogrammfunktion ist.

Objektkonstruktoren

Der Versuch, eine Pfeilfunktion zu instanziieren, führt zu einer Ausnahme:

var x = () => {};
new x(); // TypeError: x is not a constructor

Ein wichtiger Vorteil von Funktionen gegenüber Pfeilfunktionen besteht daher darin, dass Funktionen auch als Objektkonstruktoren dienen:

function Person(name) {
    this.name = name;
}

Die funktionell identische2 ECMAScript Harmony Entwurfs-Klassendefinition ist jedoch fast genauso kompakt:

class Person {
    constructor(name) {
        this.name = name;
    }
}

Ich gehe davon aus, dass die Verwendung der früheren Notation letztendlich entmutigt wird. Die Objektkonstruktor-Notation kann von einigen weiterhin für einfache anonyme Objektfabriken verwendet werden, bei denen Objekte programmgesteuert generiert werden, aber für nicht viel anderes.

Wenn ein Objektkonstruktor benötigt wird, sollte erwogen werden, die Funktion in eine class umzuwandeln, wie oben gezeigt. Die Syntax funktioniert auch mit anonymen Funktionen/Klassen.

Lesbarkeit von Pfeilfunktionen

Das wohl beste Argument dafür, bei regulären Funktionen zu bleiben – unabhängig von der Bereichssicherheit – wäre, dass Pfeilfunktionen weniger lesbar sind als reguläre Funktionen. Wenn Ihr Code im Grunde genommen nicht funktional ist, erscheinen Pfeilfunktionen möglicherweise nicht erforderlich, und wenn Pfeilfunktionen nicht konsequent verwendet werden, sehen sie hässlich aus.

ECMAScript hat sich seit ECMAScript 5.1 ziemlich verändert und uns die funktionalen Array.forEach, Array.map und all diese Funktionen des funktionalen Programmierens gegeben, die wir anstelle von for-Schleifen verwenden. Asynchrones JavaScript hat sich stark entwickelt. ES6 wird auch ein Promise-Objekt bereitstellen, was bedeutet, dass noch mehr anonyme Funktionen verwendet werden. Es gibt kein Zurück beim funktionalen Programmieren. In funktionalem JavaScript sind Pfeilfunktionen gegenüber regulären Funktionen vorzuziehen.

Betrachten Sie beispielsweise diesen (besonders verwirrenden) Code3:

function CommentController(articles) {
    this.comments = [];

    articles.getList()
        .then(articles => Promise.all(articles.map(article => article.comments.getList())))
        .then(commentLists => commentLists.reduce((a, b) => a.concat(b)));
        .then(comments => {
            this.comments = comments;
        })
}

Derselbe Code mit regulären Funktionen:

function CommentController(articles) {
    this.comments = [];

    articles.getList()
        .then(function (articles) {
            return Promise.all(articles.map(function (article) {
                return article.comments.getList();
            }));
        })
        .then(function (commentLists) {
            return commentLists.reduce(function (a, b) {
                return a.concat(b);
            });
        })
        .then(function (comments) {
            this.comments = comments;
        }.bind(this));
}

Obwohl jede Pfeilfunktion durch eine Standardfunktion ersetzt werden kann, gäbe es wenig zu gewinnen. Welche Version ist lesbarer? Ich würde sagen die erste.

Ich denke, die Frage, ob Pfeilfunktionen oder reguläre Funktionen verwendet werden sollen, wird im Laufe der Zeit weniger relevant werden. Die meisten Funktionen werden entweder zu Klassenmethoden, die das function-Stichwort eliminieren, oder sie werden Klassen. Funktionen bleiben für das Patchen von Klassen durch das Object.prototype in Gebrauch. In der Zwischenzeit schlage ich vor, das function-Stichwort für alles zu reservieren, was wirklich eine Klassenmethode oder eine Klasse sein sollte.


Anmerkungen

  1. Benannte Pfeilfunktionen wurden im ES6-Standard verschoben. Sie könnten in einer zukünftigen Version noch hinzugefügt werden.
  2. Laut dem Entwurfsspezifikation "Klassenanweisungen/-ausdrücke erstellen ein Konstruktorfunktion/Prototyp-Paar genau wie bei Funktionsanweisungen", solange eine Klasse das extend-Stichwort nicht verwendet. Ein kleiner Unterschied besteht darin, dass Klassendeklarationen Konstanten sind, während Funktionserklärungen es nicht sind.
  3. Anmerkung zu Blöcken in einzeiligen Pfeilfunktionen: Ich verwende gerne einen Block, wenn an einer Pfeilfunktion nur der Seiteneffekt (z.B. Zuweisung) gefordert ist. So ist klar, dass der Rückgabewert verworfen werden kann.

21voto

Manishz90 Punkte 2436

Pfeilfunktionen - bisher am weitesten verbreitetes ES6-Feature...

Verwendung: Alle ES5-Funktionen sollten durch ES6-Pfeilfunktionen ersetzt werden, außer in den folgenden Szenarien:

Pfeilfunktionen sollten nicht verwendet werden:

  1. Wenn wir Funktionen hoisting möchten
    • da Pfeilfunktionen anonym sind.
  2. Wenn wir this/arguments in einer Funktion verwenden möchten
    • da Pfeilfunktionen kein eigenes this/arguments haben, sondern sich auf ihren äußeren Kontext verlassen.
  3. Wenn wir eine benannte Funktion verwenden möchten
    • da Pfeilfunktionen anonym sind.
  4. Wenn wir eine Funktion als constructor verwenden möchten
    • da Pfeilfunktionen kein eigenes this haben.
  5. Wenn wir eine Funktion als Eigenschaft in einem Objektliteral hinzufügen möchten und das Objekt darin verwenden möchten
    • da wir nicht auf this (das selbst das Objekt sein sollte) zugreifen können.

Lassen Sie uns einige Varianten von Pfeilfunktionen verstehen, um sie besser zu verstehen:

Variante 1: Wenn wir mehr als ein Argument an eine Funktion übergeben möchten und einen Wert zurückgeben möchten.

ES5-Version:

var multiplizieren = function (a, b) {
    return a*b;
};
console.log(multiplizieren(5, 6)); // 30

ES6-Version:

var multiplizierenPfeil = (a, b) => a*b;
console.log(multiplizierenPfeil(5, 6)); // 30

Hinweis:

Das Schlüsselwort function ist nicht erforderlich. => ist erforderlich. {} sind optional, wenn wir keine {} bereitstellen, wird von JavaScript implizit return hinzugefügt und wenn wir {} bereitstellen, müssen wir return hinzufügen, wenn wir es benötigen.

Variante 2: Wenn wir nur ein Argument an eine Funktion übergeben möchten und einen Wert zurückgeben möchten.

ES5-Version:

var verdoppeln = function(a) {
    return a*2;
};
console.log(verdoppeln(2)); // 4

ES6-Version:

var verdoppelnPfeil  = a => a*2;
console.log(verdoppelnPfeil(2)); // 4

Hinweis:

Beim Übergeben nur eines Arguments können wir die Klammern, (), weglassen.

Variante 3: Wenn wir keine Argumente an eine Funktion übergeben möchten und keinen Wert zurückgeben möchten.

ES5-Version:

var sagenHallo = function() {
    console.log("Hallo");
};
sagenHallo(); // Hallo

ES6-Version:

var sagenHalloPfeil = () => {console.log("sagenHalloPfeil");}
sagenHalloPfeil(); // sagenHalloPfeil

Variante 4: Wenn wir explizit von Pfeilfunktionen zurückgeben möchten.

ES6-Version:

var erhöhen = x => {
  return x + 1;
};
console.log(erhöhen(1)); // 2

Variante 5: Wenn wir ein Objekt von Pfeilfunktionen zurückgeben möchten.

ES6-Version:

var objektZurückgeben = () => ({a:5});
console.log(objektZurückgeben());

Hinweis:

Wir müssen das Objekt in Klammern, (), einpacken. Andernfalls kann JavaScript nicht zwischen einem Block und einem Objekt unterscheiden.

Variante 6: Pfeilfunktionen haben kein arguments (eine art Arrayobjekt) von sich aus. Sie sind abhängig vom äußeren Kontext für arguments.

ES6-Version:

function foo() {
  var abc = i => arguments[0];
  console.log(abc(1));
};
foo(2); // 2

Hinweis:

foo ist eine ES5-Funktion, mit einem arguments arrayähnlichen Objekt und ein Argument, das ihm übergeben wird, ist 2, also ist arguments[0] für foo 2.

abc ist eine ES6-Pfeilfunktion, da sie ihr eigenes arguments nicht hat. Daher gibt sie arguments[0] von foo aus ihrem äußeren Kontext aus, anstatt.

Variante 7: Pfeilfunktionen haben kein eigenes this, sie sind abhängig vom äußeren Kontext für this

ES5-Version:

var obj5 = {
  greet: "Hallo, Willkommen ",
  greetUser : function(user) {
        setTimeout(function(){
        console.log(this.greet + ": " +  user); // "this" hier ist undefiniert.
        });
     }
};

obj5.greetUser("Katty"); //undefiniert: Katty

Hinweis:

Das an setTimeout übergebene Rückruf ist eine ES5-Funktion und hat sein eigenes this, das in einer use-strict-Umgebung undefiniert ist. Daher erhalten wir die Ausgabe:

undefiniert: Katty

ES6-Version:

var obj6 = {
  greet: "Hallo, Willkommen ",
  greetUser : function(user) {
    setTimeout(() => console.log(this.greet + ": " +  user));
      // Hier bezieht sich dies auf den äußeren Kontext
   }
};

obj6.greetUser("Katty"); // Hallo, Willkommen: Katty

Hinweis:

Das an setTimeout übergebene Rückruf ist eine ES6-Pfeilfunktion und hat kein eigenes this, also nimmt es es aus seinem äußeren Kontext, der greetUser ist, der this hat. Das ist obj6 und daher erhalten wir die Ausgabe:

Hallo, Willkommen: Katty

Verschiedenes:

  • Wir können new nicht mit Pfeilfunktionen verwenden.
  • Pfeilfunktionen haben keine prototype-Eigenschaft.
  • Wir haben kein Binden von this, wenn eine Pfeilfunktion durch apply oder call aufgerufen wird.

10voto

Jackson Punkte 8490

Ich stehe immer noch zu allem, was ich in meiner ersten Antwort in diesem Thread geschrieben habe. Allerdings hat sich meine Meinung zum Code-Stil seitdem entwickelt, daher habe ich eine neue Antwort auf diese Frage, die auf meiner letzten aufbaut.

Bezüglich lexikalischem this

In meiner letzten Antwort habe ich bewusst einen zugrunde liegenden Glauben vermieden, den ich zu dieser Sprache habe, da er nicht direkt mit dem Argument zusammenhing, das ich vorbrachte. Trotzdem kann ich verstehen, warum viele Leute einfach meinen Ratschlag, Pfeile nicht zu verwenden, zurückweisen, wenn sie Pfeile so nützlich finden.

Mein Glaube ist folgender: Wir sollten this erst gar nicht verwenden. Daher ist die "lexikalische this"-Funktion von Pfeilen für eine Person, die bewusst darauf verzichtet, this in ihrem Code zu verwenden, von geringem bis keinem Wert. Auch unter der Voraussetzung, dass this eine schlechte Sache ist, wird die Behandlung von this durch Pfeile weniger zu einer "guten Sache"; stattdessen ist es eine Form der Schadensbegrenzung für ein weiteres schlechtes Merkmal der Sprache.

Ich vermute, dass das einigen Leuten einfach nicht in den Sinn kommt, aber auch denen, denen es in den Sinn kommt, müssen sich zwangsläufig in Codebasen wiederfinden, in denen this hunderte Male pro Datei erscheint, und ein wenig (oder viel) Schadensbegrenzung alles ist, was eine vernünftige Person erhoffen kann. Daher können Pfeile in gewisser Weise gut sein, wenn sie eine schlechte Situation verbessern.

Auch wenn es einfacher ist, mit Pfeilen Code mit this zu schreiben als ohne sie, bleiben die Regeln für die Verwendung von Pfeilen sehr komplex (siehe: aktueller Thread). Daher sind Richtlinien weder "klar" noch "konsistent", wie Sie gefordert haben. Selbst wenn Programmierer über die Ambiguitäten von Pfeilen Bescheid wissen, denke ich, dass sie einfach mit den Schultern zucken und sie trotzdem akzeptieren, weil der Wert des lexikalischen this sie übertrifft.

All dies ist eine Einleitung zu der folgenden Erkenntnis: Wenn man kein this verwendet, wird die durch Pfeile normalerweise hervorgerufene Mehrdeutigkeit bezüglich von this irrelevant. Pfeile werden in diesem Kontext neutraler.

Bezüglich knapper Syntax

Als ich meine erste Antwort schrieb, war ich der Meinung, dass auch die sklavische Einhaltung von Best Practices einen lohnenswerten Preis darstellte, wenn dies bedeutete, dass ich perfekteren Code produzieren könnte. Aber letztendlich kam ich zu dem Schluss, dass Kürze auch eine Form von Abstraktion sein kann, die die Code-Qualität verbessern kann - genug, um manchmal von den Best Practices abzuweichen.

Mit anderen Worten: Verdammt, ich will auch Einzeilerfunktionen!

Bezüglich einer Richtlinie

Bei der Möglichkeit von this-neutralen Pfeilfunktionen und der Tatsache, dass Kürze erstrebenswert ist, biete ich die folgende mildere Richtlinie an:

Richtlinie für Funktionsnotation in ES6:

  • Verwenden Sie kein this.
  • Verwenden Sie Funktionsdeklarationen für Funktionen, die Sie beim Namen aufrufen würden (weil sie gehoist werden).
  • Verwenden Sie Pfeilfunktionen für Callbacks (weil sie in der Regel knapper sind).

7voto

Carsten Führmann Punkte 2570

Abgesehen von den bisherigen großartigen Antworten möchte ich einen ganz anderen Grund vorstellen, warum Pfeilfunktionen in gewisser Hinsicht grundsätzlich besser sind als "gewöhnliche" JavaScript-Funktionen.

Zum Zwecke der Diskussion nehmen wir vorübergehend an, dass wir einen Typenprüfer wie TypeScript oder Facebooks "Flow" verwenden. Betrachten Sie das folgende Spielmodul, das gültiger ECMAScript-6-Code plus Flow-Typenannotationen ist (ich werde den ungetypten Code, der realistischerweise von Babel erzeugt wird, am Ende dieser Antwort mit einbeziehen, damit er tatsächlich ausgeführt werden kann):

export class C {
  n: number;
  f1: number => number;
  f2: number => number;

  constructor(){
    this.n = 42;
    this.f1 = (x:number) => x + this.n;
    this.f2 = function (x:number) { return  x + this.n;};
  }
}

Schauen Sie nun, was passiert, wenn wir die Klasse C aus einem anderen Modul verwenden, wie hier:

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1: number = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2: number = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

Wie Sie sehen können, ist hier der Typenprüfer gescheitert: f2 sollte eine Zahl zurückgeben, aber stattdessen wurde ein String zurückgegeben!

Noch schlimmer scheint es, dass kein denkbarer Typenprüfer mit gewöhnlichen (nicht-Pfeil-) JavaScript-Funktionen umgehen kann, weil das "this" von f2 nicht in der Argumentenliste von f2 vorkommt, sodass der erforderliche Typ für "this" nicht als Annotation zu f2 hinzugefügt werden könnte.

Betrifft dieses Problem auch Personen, die keine Typenprüfer verwenden? Ich denke schon, denn selbst wenn wir keine statischen Typen haben, denken wir so, als ob sie da wären. ("Der erste Parameter muss eine Zahl sein, der zweite ein String" usw.) Ein verstecktes "this"-Argument, das im Funktionskörper verwendet werden kann oder auch nicht, erschwert unser mentales Buchführung.

Hier ist die ausführbare ungetypte Version, die von Babel erzeugt würde:

class C {
constructor() {
this.n = 42;
this.f1 = x => x + this.n;
this.f2 = function (x) { return x + this.n; };
}
}

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1 = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2 = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

3voto

zowers Punkte 568

Ich ziehe es vor, Pfeilfunktionen jederzeit zu verwenden, wenn kein Zugriff auf das lokale this erforderlich ist, da Pfeilfunktionen nicht ihr eigenes this, Argumente, Super oder new.target binden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X