Wann sollte ich Pfeilfunktionen in ECMAScript 6 verwenden?

406

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

Mit () => {}und erhalten function () {}wir zwei sehr ähnliche Möglichkeiten, um Funktionen in ES6 zu schreiben. In anderen Sprachen zeichnen sich Lambda-Funktionen häufig durch Anonymität aus, in ECMAScript kann jedoch jede Funktion anonym sein. Jeder der beiden Typen hat eindeutige Verwendungsdomänen (nämlich wenn thisentweder explizit gebunden werden muss oder explizit nicht gebunden werden muss). Zwischen diesen Domänen gibt es eine große Anzahl von Fällen, in denen eine der beiden Notationen ausreicht.

Pfeilfunktionen in ES6 haben mindestens zwei Einschränkungen:

  • Arbeiten Sie nicht mit newund können beim Erstellen nicht verwendet werdenprototype
  • Feste thisgebunden Umfang bei der Initialisierung

Abgesehen von diesen beiden Einschränkungen könnten Pfeilfunktionen reguläre Funktionen theoretisch fast überall ersetzen. Was ist der richtige Ansatz, um sie in der Praxis anzuwenden? Sollten Pfeilfunktionen verwendet werden, z.

  • "Überall, wo sie funktionieren", dh überall, wo eine Funktion nicht agnostisch gegenüber der thisVariablen sein muss und wir kein Objekt erstellen.
  • nur "überall dort, wo sie gebraucht werden", dh Ereignis-Listener, Timeouts, die an einen bestimmten Bereich gebunden sein müssen
  • mit 'kurzen' Funktionen, aber nicht mit 'langen' Funktionen
  • Nur mit Funktionen, die keine weitere Pfeilfunktion enthalten

Was ich suche, ist eine Richtlinie zur Auswahl der geeigneten Funktionsnotation in der zukünftigen Version von ECMAScript. Die Richtlinie muss klar sein, damit sie Entwicklern in einem Team beigebracht werden kann, und konsistent sein, damit kein ständiges Umgestalten von einer Funktionsnotation zur anderen erforderlich ist.

lyschoening
quelle
33
Sie betrachten Fixed this bound to scope at initialisationals Einschränkung?
thefourtheye
12
Dies ist ein Vorteil, kann aber auch eine Einschränkung sein, wenn Sie die Funktion außerhalb des ursprünglichen Kontexts wiederverwenden möchten. Zum Beispiel beim dynamischen Hinzufügen einer Funktion zu einer Klasse über Object.prototype. Was ich unter "Einschränkung" verstehe, ist, dass das Ändern des Werts von thisetwas ist, was Sie mit regulären Funktionen tun können, aber nicht mit Pfeilfunktionen.
Lyschoening
1
Ehrlich gesagt denke ich, dass Richtlinien für den Codierungsstil eher eine Meinung haben. Versteh mich nicht falsch, ich denke sie sind wichtig, aber es gibt keine einzige Richtlinie, die für alle geeignet ist.
Felix Kling
Ich denke nicht, dass dies Fixed this bound to scope at initialisationeine Einschränkung ist. :) Schauen Sie sich diesen Artikel an: exploringjs.com/es6/ch_arrow-functions.html
NgaNguyenDuy
3
@thefourtheye bedeutet "Einschränkung" hier "Einschränkung, weil ein dummer automatischer Codeübersetzer nicht blind einen durch einen anderen ersetzen und davon ausgehen kann, dass alles wie erwartet läuft".
Pacerier

Antworten:

322

Vor einiger Zeit hat unser Team den gesamten Code (eine mittelgroße AngularJS-App) auf JavaScript migriert, das mit Traceur Babel kompiliert wurde . Ich verwende jetzt die folgende Faustregel für Funktionen in ES6 und darüber hinaus:

  • Verwendung functionim globalen Bereich und für Object.prototypeEigenschaften.
  • Verwendung classfür Objektkonstruktoren.
  • Verwenden Sie =>überall sonst.

Warum fast überall Pfeilfunktionen verwenden?

  1. Umfangssicherheit: Wenn Pfeilfunktionen konsistent verwendet werden, wird garantiert, dass alles das gleiche thisObjectwie die Wurzel verwendet. Wenn auch nur ein einzelner Standardfunktionsrückruf mit einer Reihe von Pfeilfunktionen gemischt wird, besteht die Möglichkeit, dass der Bereich durcheinander gerät.
  2. Kompaktheit: Pfeilfunktionen sind leichter zu lesen und zu schreiben. (Dies scheint eine Meinung zu sein, daher werde ich weiter unten einige Beispiele nennen.)
  3. Klarheit: Wenn fast alles eine Pfeilfunktion ist, functionsticht jeder Reguläre sofort hervor, um den Umfang zu definieren. Ein Entwickler kann immer die nächsthöhere functionAnweisung nachschlagen, um zu sehen, was die thisObjectist.

Warum immer reguläre Funktionen im globalen Bereich oder im Modulbereich verwenden?

  1. Um eine Funktion anzugeben, die nicht auf die zugreifen soll thisObject.
  2. Das windowObjekt (globaler Bereich) wird am besten explizit angesprochen.
  3. Viele Object.prototypeDefinitionen leben im globalen Bereich (denken String.prototype.truncateusw.) und diese müssen im Allgemeinen functionsowieso vom Typ sein. Die konsequente Verwendung functiondes globalen Bereichs hilft, Fehler zu vermeiden.
  4. Viele Funktionen im globalen Bereich sind Objektkonstruktoren für Klassendefinitionen alten Stils.
  5. Funktionen können 1 genannt werden . Dies hat zwei Vorteile: (1) Das Schreiben ist weniger umständlich function foo(){}als const foo = () => {}- insbesondere außerhalb anderer Funktionsaufrufe. (2) Der Funktionsname wird in Stapelspuren angezeigt. Während 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 hochgezogen (dh, auf sie kann zugegriffen werden, bevor sie deklariert werden). Dies ist ein nützliches Attribut in einer statischen Dienstprogrammfunktion.


Objektkonstruktoren

Der Versuch, eine Pfeilfunktion zu instanziieren, löst eine Ausnahme aus:

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

Ein wesentlicher Vorteil von Funktionen gegenüber Pfeilfunktionen besteht daher darin, dass Funktionen als Objektkonstruktoren fungieren:

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

Die funktionsidentische 2 ES Harmony- Entwurfsklassendefinition ist jedoch fast genauso kompakt:

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

Ich gehe davon aus, dass von der Verwendung der früheren Notation irgendwann abgeraten wird. Die Objektkonstruktornotation kann von einigen weiterhin für einfache anonyme Objektfabriken verwendet werden, in denen Objekte programmgesteuert generiert werden, aber nicht für viele andere.

Wenn ein Objektkonstruktor benötigt wird, sollte in Betracht gezogen werden, die Funktion classwie oben gezeigt in eine zu konvertieren . Die Syntax funktioniert auch mit anonymen Funktionen / Klassen.


Lesbarkeit der Pfeilfunktionen

Das wahrscheinlich beste Argument für das Festhalten an regulären Funktionen - verdammt noch mal die Sicherheit des Oszilloskops - wäre, dass Pfeilfunktionen weniger lesbar sind als reguläre Funktionen. Wenn Ihr Code überhaupt nicht funktioniert, scheinen Pfeilfunktionen möglicherweise nicht erforderlich zu sein, und wenn Pfeilfunktionen nicht konsistent verwendet werden, sehen sie hässlich aus.

ECMAScript hat sich seit ECMAScript 5.1 erheblich verändert Array.forEach, Array.mapund all diese funktionalen Programmierfunktionen verwenden Funktionen, bei denen zuvor for-Schleifen verwendet worden wären. Asynchrones JavaScript hat sich ziemlich stark verbessert. ES6 wird auch ein PromiseObjekt versenden , was noch mehr anonyme Funktionen bedeutet. Für die funktionale Programmierung gibt es kein Zurück. In funktionalem JavaScript sind Pfeilfunktionen regulären Funktionen vorzuziehen.

Nehmen Sie zum Beispiel diesen (besonders verwirrenden) Teil von Code 3 :

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;
        })
}

Der gleiche 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));
}

Während jede der Pfeilfunktionen durch eine Standardfunktion ersetzt werden kann, wäre davon nur sehr wenig zu profitieren. Welche Version ist besser lesbar? Ich würde den ersten sagen.

Ich denke, die Frage, ob Pfeilfunktionen oder reguläre Funktionen verwendet werden sollen, wird mit der Zeit weniger relevant. Die meisten Funktionen werden entweder zu Klassenmethoden, die das functionSchlüsselwort entfernen , oder sie werden zu Klassen. Funktionen werden weiterhin zum Patchen von Klassen über das verwendet Object.prototype. In der Zwischenzeit schlage ich vor, das functionSchlüsselwort für alles zu reservieren, was eigentlich eine Klassenmethode oder eine Klasse sein sollte.


Anmerkungen

  1. Benannte Pfeilfunktionen wurden in der ES6-Spezifikation zurückgestellt . Möglicherweise wird ihnen noch eine zukünftige Version hinzugefügt.
  2. Gemäß dem Entwurf der Spezifikation "Klassendeklarationen / -ausdrücke erstellen ein Konstruktorfunktion / Prototyp-Paar genau wie bei Funktionsdeklarationen" , solange eine Klasse das extendSchlüsselwort nicht verwendet . Ein kleiner Unterschied besteht darin, dass Klassendeklarationen Konstanten sind, Funktionsdeklarationen jedoch nicht.
  3. Hinweis zu Blöcken in Pfeilfunktionen mit einer Anweisung: Ich verwende einen Block gerne überall dort, wo eine Pfeilfunktion nur für den Nebeneffekt aufgerufen wird (z. B. Zuweisung). Auf diese Weise ist klar, dass der Rückgabewert verworfen werden kann.
lyschoening
quelle
4
Die andere Zeit, die Sie verwenden möchten, functionist, wenn Sie nicht thisgebunden werden möchten , oder? Mein häufigstes Szenario hierfür sind Ereignisse, bei denen Sie möglicherweise thisauf das Objekt (normalerweise den DOM-Knoten) verweisen möchten , das das Ereignis ausgelöst hat.
Brett
13
Ich denke tatsächlich, in Beispiel 3 sind die regulären Funktionen besser lesbar. Sogar Nicht-Programmierer könnten erraten, was passiert. Mit den Pfeilen müssen Sie genau wissen, wie sie funktionieren, um dieses Beispiel zu verstehen. Vielleicht würden mehr Zeilenumbrüche dem Pfeilbeispiel helfen, aber ich weiß es nicht. Nur meine 2 Cent, aber Pfeile lassen mich zusammenzucken (aber ich habe sie noch nicht benutzt, so dass ich bald konvertiert werden kann.)
Spencer
3
@Spencer das ist ein fairer Punkt. Nach meiner eigenen Erfahrung =>sieht es mit der Zeit besser aus. Ich bezweifle, dass Nicht-Programmierer die beiden Beispiele sehr unterschiedlich sehen würden. Wenn Sie ES2016-Code schreiben, werden Sie normalerweise auch nicht so viele Pfeilfunktionen verwenden. In diesem Beispiel würden Sie bei Verwendung von async / await und einem Array-Verständnis nur eine Pfeilfunktion im reduce()Aufruf erhalten.
Lyschoening
3
Ich stimme Spencer voll und ganz zu, dass reguläre Funktionen in diesem Beispiel weitaus besser lesbar sind.
Jonschlinkert
2
Gute Antwort, danke! persönlich benutze ich auch Pfeile im globalen Bereich so oft wie möglich. Dies lässt mir fast keine "Funktion". Für mich bedeutet eine 'Funktion' im Code einen Sonderfall, der hervorstechen und sorgfältig abgewogen werden muss.
Kofifus
80

Dem Vorschlag zufolge zielten Pfeile darauf ab, "mehrere häufig auftretende Probleme zu lösen und zu lösen Function Expression". Sie wollten die Sache verbessern, indem sie thislexikalisch binden und eine knappe Syntax bieten.

Jedoch,

  • Man kann nicht konsequent thislexikalisch binden
  • Die Syntax der Pfeilfunktion ist heikel und mehrdeutig

Daher können Pfeilfunktionen zu Verwirrung und Fehlern führen und sollten aus dem Vokabular eines JavaScript-Programmierers ausgeschlossen und durch functionausschließlich ersetzt werden.

In Bezug auf lexikalische this

this ist problematisch:

function Book(settings) {
    this.settings = settings;
    this.pages = this.createPages();
}
Book.prototype.render = function () {
    this.pages.forEach(function (page) {
        page.draw(this.settings);
    }, this);
};

Mit den Pfeilfunktionen soll das Problem behoben werden, bei dem auf eine Eigenschaft thisinnerhalb eines Rückrufs zugegriffen werden muss. Dazu gibt es bereits mehrere Möglichkeiten: Sie können thiseiner Variablen zuweisen , binddas dritte Argument verwenden oder verwenden, das für die ArrayAggregatmethoden verfügbar ist . Pfeile scheinen jedoch die einfachste Problemumgehung zu sein, sodass die Methode folgendermaßen überarbeitet werden könnte:

this.pages.forEach(page => page.draw(this.settings));

Überlegen Sie jedoch, ob der Code eine Bibliothek wie jQuery verwendet, deren Methoden thisspeziell gebunden sind . Nun gibt es zwei thisWerte, mit denen man sich befassen muss:

Book.prototype.render = function () {
    var book = this;
    this.$pages.each(function (index) {
        var $page = $(this);
        book.draw(book.currentPage + index, $page);
    });
};

Wir müssen verwenden , functionum eachzu binden thisdynamisch. Wir können hier keine Pfeilfunktion verwenden.

Der Umgang mit mehreren thisWerten kann auch verwirrend sein, da es schwierig ist zu wissen, über was thisein Autor gesprochen hat:

function Reader() {
    this.book.on('change', function () {
        this.reformat();
    });
}

Wollte der Autor tatsächlich anrufen Book.prototype.reformat? Oder hat er vergessen zu binden thisund beabsichtigt anzurufen Reader.prototype.reformat? Wenn wir den Handler in eine Pfeilfunktion ändern, werden wir uns ebenfalls fragen, ob der Autor die Dynamik wollte this, aber einen Pfeil gewählt hat, weil er in eine Zeile passt:

function Reader() {
    this.book.on('change', () => this.reformat());
}

Man könnte sagen: "Ist es außergewöhnlich, dass Pfeile manchmal die falsche Funktion sind? Wenn wir nur selten dynamische thisWerte benötigen , ist es immer noch in Ordnung, die meiste Zeit Pfeile zu verwenden."

Aber fragen Sie sich Folgendes: "Wäre es 'wert', Code zu debuggen und festzustellen, dass das Ergebnis eines Fehlers durch einen 'Randfall' verursacht wurde?" Ich würde es vorziehen, Probleme nicht nur die meiste Zeit zu vermeiden, sondern 100% der Zeit.

Es gibt einen besseren Weg: Immer verwenden function( thiskann also immer dynamisch gebunden werden) und immer thisüber eine Variable referenzieren . Variablen sind lexikalisch und nehmen viele Namen an. Das Zuweisen thiszu einer Variablen macht Ihre Absichten klar:

function Reader() {
    var reader = this;
    reader.book.on('change', function () {
        var book = this;
        book.reformat();
        reader.reformat();
    });
}

Darüber hinaus wird durch die ständige Zuweisung thiszu einer Variablen (auch wenn eine einzelne thisoder keine andere Funktion vorhanden ist) sichergestellt, dass die eigenen Absichten auch nach einer Änderung des Codes klar bleiben.

Auch Dynamik thisist kaum außergewöhnlich. jQuery wird auf über 50 Millionen Websites verwendet (Stand: Februar 2016). Hier sind andere APIs, die thisdynamisch binden :

  • Mocha (~ 120k Downloads gestern) stellt Methoden für seine Tests über zur Verfügung this.
  • Grunt (~ 63k Downloads gestern) macht Methoden für Build-Aufgaben über verfügbar this.
  • Backbone (~ 22.000 Downloads gestern) definiert Methoden für den Zugriff this.
  • Ereignis-APIs (wie die DOMs) beziehen sich auf ein EventTargetwith this.
  • Prototyp-APIs, die gepatcht oder erweitert werden, beziehen sich auf Instanzen mit this.

(Statistiken über http://trends.builtwith.com/javascript/jQuery und https://www.npmjs.com .)

Sie benötigen wahrscheinlich thisbereits dynamische Bindungen.

Ein Lexikon thiswird manchmal erwartet, aber manchmal nicht; so wie eine Dynamik thismanchmal erwartet wird, aber manchmal nicht. Zum Glück gibt es einen besseren Weg, der immer die erwartete Bindung erzeugt und kommuniziert.

In Bezug auf die knappe Syntax

Pfeilfunktionen haben es geschafft, eine "kürzere syntaktische Form" für Funktionen bereitzustellen. Aber werden diese kürzeren Funktionen Sie erfolgreicher machen?

Ist x => x * x"leichter zu lesen" als function (x) { return x * x; }? Vielleicht liegt es daran, dass es wahrscheinlicher ist, dass eine einzelne, kurze Codezeile erzeugt wird. Nach Dysons Einfluss von Lesegeschwindigkeit und Zeilenlänge auf die Effektivität des Lesens vom Bildschirm ,

Eine mittlere Zeilenlänge (55 Zeichen pro Zeile) scheint ein effektives Lesen bei normaler und hoher Geschwindigkeit zu unterstützen. Dies führte zu einem Höchstmaß an Verständnis. . .

Ähnliche Begründungen werden für den bedingten (ternären) Operator und für einzeilige ifAnweisungen gemacht.

Schreiben Sie jedoch wirklich die einfachen mathematischen Funktionen, die im Vorschlag angegeben sind ? Meine Domänen sind nicht mathematisch, daher sind meine Unterprogramme selten so elegant. Vielmehr sehe ich häufig, dass Pfeilfunktionen eine Spaltenbegrenzung überschreiten und aufgrund des Editors oder Styleguides in eine andere Zeile umbrechen, wodurch die "Lesbarkeit" nach Dysons Definition ungültig wird.

Man könnte sich fragen: "Wie wäre es, wenn Sie nur die Kurzversion für kurze Funktionen verwenden, wenn möglich?" Aber jetzt widerspricht eine Stilregel einer Sprachbeschränkung: "Versuchen Sie, die kürzestmögliche Funktionsnotation zu verwenden, wobei zu beachten ist, dass manchmal nur die längste Notation thiswie erwartet gebunden wird ." Eine solche Verschmelzung macht Pfeile besonders anfällig für Missbrauch.

Es gibt zahlreiche Probleme mit der Pfeilfunktionssyntax:

const a = x =>
    doSomething(x);

const b = x =>
    doSomething(x);
    doSomethingElse(x);

Beide Funktionen sind syntaktisch gültig. Aber es doSomethingElse(x);ist nicht im Körper von b, es ist nur eine schlecht eingerückte Aussage auf höchster Ebene.

Beim Erweitern auf die Blockform gibt es kein Implizit mehr return, dessen Wiederherstellung man vergessen könnte. Aber der Ausdruck sollte möglicherweise nur eine Nebenwirkung hervorrufen. Wer weiß also, ob in returnZukunft ein expliziter Ausdruck erforderlich sein wird?

const create = () => User.create();

const create = () => {
    let user;
    User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

const create = () => {
    let user;
    return User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

Was als Ruheparameter gedacht sein kann, kann als Spread-Operator analysiert werden:

processData(data, ...results => {}) // Spread
processData(data, (...results) => {}) // Rest

Die Zuweisung kann mit Standardargumenten verwechselt werden:

const a = 1;
let x;
const b = x => {}; // No default
const b = x = a => {}; // "Adding a default" instead creates a double assignment
const b = (x = a) => {}; // Remember to add parens

Blöcke sehen aus wie Objekte:

(id) => id // Returns `id`
(id) => {name: id} // Returns `undefined` (it's a labeled statement)
(id) => ({name: id}) // Returns an object

Was bedeutet das?

() => {}

Wollte der Autor ein No-Op oder eine Funktion erstellen, die ein leeres Objekt zurückgibt? ( Sollten wir in diesem Sinne jemals {danach platzieren =>? Sollten wir uns nur auf die Ausdruckssyntax beschränken? Dies würde die Häufigkeit der Pfeile weiter verringern.)

=>sieht aus wie <=und >=:

x => 1 ? 2 : 3
x <= 1 ? 2 : 3

if (x => 1) {}
if (x >= 1) {}

Um einen Pfeilfunktionsausdruck sofort aufzurufen, muss er ()außen platziert ()werden. Die Platzierung innen ist jedoch gültig und kann beabsichtigt sein.

(() => doSomething()()) // Creates function calling value of `doSomething()`
(() => doSomething())() // Calls the arrow function

Wenn man jedoch (() => doSomething()());mit der Absicht schreibt, einen sofort aufgerufenen Funktionsausdruck zu schreiben, passiert einfach nichts.

Es ist schwer zu argumentieren, dass Pfeilfunktionen in Anbetracht aller oben genannten Fälle "verständlicher" sind. Man könnte alle speziellen Regeln lernen, die erforderlich sind, um diese Syntax zu verwenden. Lohnt es sich wirklich?

Die Syntax von functionist ausnahmsweise verallgemeinert. Zur Nutzung functionbedeutet , ausschließlich die Sprache selbst eine vom Schreiben verwirrend Code verhindert. Um Prozeduren zu schreiben, die in allen Fällen syntaktisch verstanden werden sollten, wähle ich function.

In Bezug auf eine Richtlinie

Sie fordern eine Richtlinie an, die "klar" und "konsistent" sein muss. Die Verwendung von Pfeilfunktionen führt schließlich zu syntaktisch gültigem, logisch ungültigem Code, wobei beide Funktionsformen sinnvoll und willkürlich miteinander verflochten sind. Deshalb biete ich folgendes an:

Richtlinie für die Funktionsnotation in ES6:

  • Erstellen Sie immer Prozeduren mit function.
  • Immer thiseiner Variablen zuweisen . Nicht verwenden () => {}.
Jackson
quelle
5
Interessantes Schreiben über die Sicht eines funktionierenden Programmierers auf JavaScript. Ich bin nicht sicher, ob ich dem Argument der privaten Variablen zustimme. IMO wenige Leute brauchen sie wirklich; Diejenigen, die dies tun, werden wahrscheinlich auch andere Vertragsfunktionen benötigen und sich sowieso für eine Spracherweiterung wie TypeScript entscheiden. Ich kann sicherlich den Reiz eines selfstatt eines dies sehen. Ihre angegebenen Fallstricke für Pfeilfunktionen sind ebenfalls alle gültig, und die gleichen Standards wie bei anderen Aussagen, die ohne geschweifte Klammern auskommen können, gelten auch hier. Ansonsten könnte man mit Ihrem Argument genauso gut überall für Pfeilfunktionen eintreten.
Lyschoening
7
"Wenn wir mehrere Möglichkeiten haben, Dinge zu tun, entstehen unnötige Vektoren für Argumente und Meinungsverschiedenheiten am Arbeitsplatz und in der Sprachgemeinschaft. Es wäre besser, wenn die Sprachgrammatik es uns nicht erlauben würde, schlechte Entscheidungen zu treffen." Stimme so sehr zu. Schöne Zusammenfassung! Ich denke, Pfeilfunktionen sind eigentlich ein Schritt zurück. Zu einem anderen Thema wünschte ich mir, meine Mitarbeiter würden aufhören, JavaScript mit einer Reihe von Prototypdefinitionen in C # umzuwandeln. Das ist ist ekelhaft. Ich sollte Ihren Beitrag anonym verlinken :)
Spencer
11
Sehr gut geschrieben! Obwohl ich mit den meisten Ihrer Punkte nicht einverstanden bin, ist es wichtig, den entgegengesetzten Standpunkt zu berücksichtigen.
Minexew
4
Nicht Pfeilfunktionen, sondern das seltsame Verhalten von thisist das Problem von Javascript. Anstatt implizit gebunden zu sein, thissollte als explizites Argument übergeben werden.
Bob
5
" Verwenden Sie immer die Funktion (damit diese immer dynamisch gebunden werden kann) und verweisen Sie immer über eine Variable darauf. " Ich könnte nicht mehr widersprechen!
48

Pfeilfunktionen wurden erstellt, um die Funktion zu vereinfachen scopeund das thisSchlüsselwort zu lösen, indem es einfacher gestaltet wird. Sie verwenden die =>Syntax, die wie ein Pfeil aussieht.

Hinweis: Die vorhandenen Funktionen werden nicht ersetzt. Wenn Sie jede Funktionssyntax durch Pfeilfunktionen ersetzen, funktioniert dies nicht in allen Fällen.

Werfen wir einen Blick auf die vorhandene ES5-Syntax. Wenn sich das thisSchlüsselwort in der Methode eines Objekts befindet (eine Funktion, die zu einem Objekt gehört), auf was würde es sich beziehen?

var Actor = {
  name: 'RajiniKanth',
  getName: function() {
     console.log(this.name);
  }
};
Actor.getName();

Das obige Snippet würde sich auf einen beziehen objectund den Namen ausdrucken "RajiniKanth". Lassen Sie uns den folgenden Ausschnitt untersuchen und sehen, worauf dies hier hinweisen würde.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

Was ist nun, wenn sich das thisSchlüsselwort in befindet method’s function?

Hier würde sich dies window objectals das inner functionals herausgefallen bezeichnen scope. Weil this, immer den Besitzer der Funktion verweist ist es in, für diesen Fall - da er nun den Gültigkeitsbereich ist - das Fenster / global - Objekt.

Wenn es sich innerhalb einer objectMethode functionbefindet, ist der Eigentümer des Objekts das Objekt. Somit ist das Schlüsselwort this an das Objekt gebunden. Wenn es sich jedoch innerhalb einer Funktion befindet, entweder allein oder innerhalb einer anderen Methode, bezieht es sich immer auf das window/globalObjekt.

var fn = function(){
  alert(this);
}

fn(); // [object Window]

Es gibt Möglichkeiten, dieses Problem ES5selbst zu lösen. Lassen Sie uns dies untersuchen, bevor wir uns mit den ES6-Pfeilfunktionen befassen, wie es gelöst werden kann.

Normalerweise erstellen Sie eine Variable außerhalb der inneren Funktion der Methode. Jetzt ‘forEach’erhält die Methode Zugriff auf thisund damit auf die object’sEigenschaften und deren Werte.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   var _this = this;
   this.movies.forEach(function(movie) {
     alert(_this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

Verwenden Sie bind, um das thisSchlüsselwort, das sich auf die Methode bezieht, an das anzuhängen method’s inner function.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   }.bind(this));
  }
};

Actor.showMovies();

Mit der ES6Pfeilfunktion können wir das lexical scopingProblem jetzt einfacher lösen.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach((movie) => {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

Arrow functionssind eher Funktionsanweisungen, nur dass sie binddies tun parent scope. Wenn das arrow function is in top scope, thisbezieht Argument window/global scope, während ein Pfeil Funktion innerhalb einer regulären Funktion wird sein , dieses Argument hat das gleiche wie seine äußeree Funktion.

With- arrowFunktionen thissind zum scopeZeitpunkt der Erstellung an das Gehäuse gebunden und können nicht geändert werden. Der neue Operator, Binden, Aufrufen und Anwenden hat keine Auswirkung darauf.

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

// With a traditional function if we don't control
// the context then can we lose control of `this`.
var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`
  asyncFunction(o, function (param) {
  // We made a mistake of thinking `this` is
  // the instance of `o`.
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? false

Im obigen Beispiel haben wir die Kontrolle darüber verloren. Wir können das obige Beispiel lösen, indem wir eine variable Referenz von thisoder verwenden bind. Mit ES6 wird es einfacher, das zu verwalten, an das es thisgebunden ist lexical scoping.

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`.
  //
  // Because this arrow function is created within
  // the scope of `doSomething` it is bound to this
  // lexical scope.
  asyncFunction(o, (param) => {
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? true

Wenn nicht zu Pfeilfunktionen

In einem Objektliteral.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  getName: () => {
     alert(this.name);
  }
};

Actor.getName();

Actor.getNameist mit einem Pfeil Funktion definiert, aber beim Aufruf es undefined - Benachrichtigungen , da this.nameist undefinedals der Kontext bleibt window.

Dies geschieht, weil die Pfeilfunktion den Kontext lexikalisch mit dem window object... dh dem äußeren Bereich verbindet. Die Ausführung this.nameentspricht äquivalent zu window.name, was nicht definiert ist.

Objektprototyp

Die gleiche Regel gilt für die Definition von Methoden auf a prototype object. Anstatt eine Pfeilfunktion zum Definieren der sayCatName-Methode zu verwenden, wird Folgendes falsch angegeben context window:

function Actor(name) {
  this.name = name;
}
Actor.prototype.getName = () => {
  console.log(this === window); // => true
  return this.name;
};
var act = new Actor('RajiniKanth');
act.getName(); // => undefined

Konstruktoren aufrufen

thisIn einem Konstruktionsaufruf ist das neu erstellte Objekt. Bei der Ausführung von new Fn () ist der Kontext von constructor Fnein neues Objekt : this instanceof Fn === true.

this wird aus dem umschließenden Kontext eingerichtet, dh aus dem äußeren Bereich, der es dem neu erstellten Objekt nicht zuweist.

var Message = (text) => {
  this.text = text;
};
// Throws "TypeError: Message is not a constructor"
var helloMessage = new Message('Hello World!');

Rückruf mit dynamischem Kontext

Die Pfeilfunktion bindet die contextstatisch bei Deklaration und kann nicht dynamisch gemacht werden. Das Anhängen von Ereignis-Listenern an DOM-Elemente ist eine häufige Aufgabe bei der clientseitigen Programmierung. Ein Ereignis löst die Handlerfunktion mit diesem als Zielelement aus.

var button = document.getElementById('myButton');
button.addEventListener('click', () => {
  console.log(this === window); // => true
  this.innerHTML = 'Clicked button';
});

thisist ein Fenster in einer Pfeilfunktion, die im globalen Kontext definiert ist. Wenn ein Klickereignis auftritt, versucht der Browser, die Handlerfunktion mit Schaltflächenkontext aufzurufen, die Pfeilfunktion ändert jedoch nicht den vordefinierten Kontext. this.innerHTMList gleichbedeutend mit window.innerHTMLund hat keinen Sinn.

Sie müssen einen Funktionsausdruck anwenden, mit dem Sie dies je nach Zielelement ändern können:

var button = document.getElementById('myButton');
button.addEventListener('click', function() {
  console.log(this === button); // => true
  this.innerHTML = 'Clicked button';
});

Wenn der Benutzer auf die Schaltfläche klickt, ist dies in der Handlerfunktion die Schaltfläche. Somit this.innerHTML = 'Clicked button'ändert korrekt auf den Button Text geklickt Status zu reflektieren.

Referenzen: https://dmitripavlutin.com/when-not-to-use-arrow-functions-in-javascript/

Thalaivar
quelle
Nun, ich muss zugeben, dass "das Beste in der Mitte liegt" . Für die Aussage positiv bewertet, dass Pfeilfunktionen keine möglichen Funktionsanwendungsfälle abdecken. Sie sind wirklich darauf ausgelegt, nur einen Teil der häufigsten Probleme zu lösen. Einfach ganz auf sie umsteigen wird ein Overkill sein.
BlitZ
@DmitriPavlutin: Überprüfen Sie meinen aktualisierten Beitrag, es ist eine Sammlung von vielen Dingen ... Vielleicht sollte ich eine Referenz veröffentlichen.
Thalaivar
2
Ihr Code nach der Zeile "Verwenden von bind, um das Schlüsselwort this, das sich auf die Methode bezieht, an die innere Funktion der Methode anzuhängen". hat Fehler darin. Haben Sie den Rest Ihrer Beispiele getestet?
Isaac Pak
Der eine using bind to attach the this keyword that refers to the method to the method’s inner function.hat Syntaxfehler.
Coda Chang
Sollte seinvar Actor = { name: 'RajiniKanth', movies: ['Kabali', 'Sivaji', 'Baba'], showMovies: function() { this.movies.forEach(function(movie){ alert(this.name + ' has acted in ' + movie); }.bind(this)) } }; Actor.showMovies();
Coda Chang
14

Pfeilfunktionen - bisher am weitesten verbreitete ES6-Funktion ...

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

Pfeilfunktionen sollten NICHT verwendet werden:

  1. Wenn wir Funktionsheben wollen
    • als Pfeilfunktionen sind anonym.
  2. Wenn wir this/ argumentsin einer Funktion verwenden möchten
    • als Pfeil Funktionen haben keine this/ argumentsihre eigenen, hängen sie auf ihrem äußeren Rahmen.
  3. Wenn wir die benannte Funktion verwenden möchten
    • als Pfeilfunktionen sind anonym.
  4. Wenn wir die Funktion als verwenden möchten constructor
    • als Pfeilfunktionen haben keine eigenen this.
  5. Wenn wir eine Funktion als Eigenschaft im Objektliteral hinzufügen und das Objekt darin verwenden möchten
    • da können wir nicht zugreifen this(was Objekt selbst sein sollte).

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

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

ES5-Version :

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

ES6-Version :

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

Hinweis: Das functionSchlüsselwort ist NICHT erforderlich. =>ist erforderlich. {}sind optional, wenn wir nicht bereitstellen, {} returnwird implizit von JavaScript hinzugefügt, und wenn wir bereitstellen {}, müssen wir hinzufügen, returnwenn wir es benötigen.

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

ES5-Version :

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

ES6-Version :

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

Hinweis: Wenn Sie nur ein Argument übergeben, können Sie Klammern weglassen ().

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

ES5-Version :

var sayHello = function() {
    console.log("Hello");
};
sayHello(); //Hello

ES6-Version :

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

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

ES6-Version :

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

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

ES6-Version :

var returnObject = () => ({a:5});
console.log(returnObject());

Hinweis: Wir müssen das Objekt in Klammern setzen, ()sonst kann JavaScript nicht zwischen einem Block und einem Objekt unterscheiden.

Variante 6 : Pfeilfunktionen haben KEIN argumentseigenes (Array-ähnliches Objekt), für das sie vom äußeren Kontext abhängen arguments.

ES6-Version :

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

Hinweis: fooeine ES5 Funktion, mit einem argumentsArray wie Objekt und ein Argument übergeben , um es ist 2so arguments[0]zu foo2 ist .

abcist eine ES6 Funktion Pfeil , da es selbst nicht hat , es ist argumentses daher druckt arguments[0]der fooanstelle äußeren Kontext es ist.

Variante 7 : Pfeilfunktionen haben KEINE thiseigenen Funktionen, für die sie vom äußeren Kontext abhängenthis

ES5-Version :

var obj5 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
        setTimeout(function(){
        console.log(this.greet + ": " +  user); // "this" here is undefined.
        });
     }
};

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

Hinweis: Der an setTimeout übergebene Rückruf ist eine ES5-Funktion und hat eine eigene, thisdie in der use-strictUmgebung undefiniert ist. Daher erhalten wir folgende Ausgabe:

undefined: Katty

ES6-Version :

var obj6 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
    setTimeout(() => console.log(this.greet + ": " +  user)); 
      // this here refers to outer context
   }
};

obj6.greetUser("Katty"); //Hi, Welcome: Katty

Hinweis: Die Callback übergeben setTimeoutist eine ES6 Funktion Pfeil und es verfügt nicht über eine eigene ist , thisso dass es dauert es von ihm äußere Rahmen ist, der ist , greetUserwas hat thisdas heißt obj6also bekommen wir Ausgabe:

Hi, Welcome: Katty

Verschiedenes: Wir können nicht newmit Pfeilfunktionen verwenden. Pfeilfunktionen haben keine prototypeEigenschaft. Wir haben KEINE Bindung, thiswann die Pfeilfunktion durch applyoder aufgerufen wird call.

Manishz90
quelle
6

Zusätzlich zu den bisher großartigen Antworten möchte ich einen ganz anderen Grund vorstellen, warum Pfeilfunktionen in gewissem Sinne grundsätzlich besser sind als "normale" JavaScript-Funktionen. Nehmen wir zur Diskussion vorübergehend an, wir verwenden eine Typprüfung wie TypeScript oder Facebooks "Flow". Betrachten Sie das folgende Spielzeugmodul, bei dem es sich um einen gültigen ECMAScript 6-Code plus Anmerkungen zum Flusstyp handelt: (Ich werde den untypisierten Code, der realistisch aus Babel resultieren würde, am Ende dieser Antwort einfügen, 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;};
  }
}

Nun sehen Sie, was passiert, wenn wir die Klasse C aus einem anderen Modul wie folgt verwenden:

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 die Typprüfung hier fehlgeschlagen : f2 sollte eine Zahl zurückgeben, aber eine Zeichenfolge!

Schlimmer noch, es scheint, dass kein denkbarer Typprüfer normale JavaScript-Funktionen (ohne Pfeil) verarbeiten kann, da das "this" von f2 in der Argumentliste von f2 nicht vorkommt, sodass der erforderliche Typ für "this" möglicherweise nicht hinzugefügt werden konnte als Anmerkung zu f2.

Betrifft dieses Problem auch Personen, die keine Typprüfer verwenden? Ich denke schon, denn selbst wenn wir keine statischen Typen haben, denken wir, als ob sie da wären. ("Die ersten Parameter müssen eine Zahl sein, der zweite eine Zeichenfolge" usw.) Ein verstecktes "this" -Argument, das im Körper der Funktion verwendet werden kann oder nicht, erschwert unsere mentale Buchhaltung.

Hier ist die ausführbare, untypisierte Version, die von Babel produziert wird:

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!

Carsten Führmann
quelle
3

Ich stehe immer noch zu allem, was ich in meiner ersten Antwort in diesem Thread geschrieben habe. Meine Meinung zum Codestil hat sich jedoch seitdem weiterentwickelt, sodass ich eine neue Antwort auf diese Frage habe, die auf meiner letzten aufbaut.

In Bezug auf lexikalische this

In meiner letzten Antwort habe ich absichtlich einen Grundgedanken vermieden, den ich über diese Sprache habe, da er nicht direkt mit dem Argument zusammenhängt, das ich vorbrachte. Ohne dies ausdrücklich zu erwähnen, kann ich jedoch verstehen, warum viele Menschen meiner Empfehlung, keine Pfeile zu verwenden, einfach widersprechen, wenn sie Pfeile so nützlich finden.

Mein Glaube ist folgender: Wir sollten ihn überhaupt nicht verwenden this. Wenn eine Person die Verwendung thisin ihrem Code absichtlich vermeidet , ist die „lexikalische this“ Funktion von Pfeilen daher von geringem bis gar keinem Wert. Unter der Voraussetzung, dass dies thiseine schlechte Sache ist, ist die Behandlung von Pfeilen thisweniger eine „gute Sache“. Stattdessen ist es eher eine Form der Schadensbegrenzung für eine andere schlechte Sprachfunktion.

Ich denke, dass dies entweder einigen Menschen nicht einfällt, aber selbst denen, denen es passiert, müssen sie sich immer in Codebasen befinden, in denen thishundert Mal pro Datei vorkommt, und ein wenig (oder viel) Schadensbegrenzung ist alles eine vernünftige Person könnte hoffen. Pfeile können also in gewisser Weise gut sein, wenn sie eine schlechte Situation verbessern.

Auch wenn es einfacher ist, Code mit thisund ohne Pfeile zu schreiben , bleiben die Regeln für die Verwendung von Pfeilen sehr komplex (siehe: aktueller Thread). Daher sind Richtlinien weder "klar" noch "konsistent", wie Sie es gewünscht haben. Selbst wenn Programmierer die Mehrdeutigkeiten von Pfeilen kennen, zucken sie mit den Schultern und akzeptieren sie trotzdem, weil der Wert von lexikalisch sie thisüberschattet.

All dies ist ein Vorwort zu folgender Erkenntnis: Wenn man es nicht benutzt this, wird die Mehrdeutigkeit über thisdiese Pfeile normalerweise irrelevant. Pfeile werden in diesem Zusammenhang neutraler.

In Bezug auf die knappe Syntax

Als ich meine erste Antwort schrieb, war ich der Meinung, dass selbst die sklavische Einhaltung von Best Practices einen lohnenden Preis darstellt, wenn dies bedeutet, dass ich perfekteren Code produzieren kann. Aber irgendwann wurde mir klar, dass Knappheit als eine Form der Abstraktion dienen kann, die auch die Codequalität verbessern kann - genug, um manchmal zu rechtfertigen, von Best Practices abzuweichen.

Mit anderen Worten: Verdammt, ich möchte auch Einzeilerfunktionen!

In Bezug auf eine Richtlinie

Mit der Möglichkeit von thisneutralen Pfeilfunktionen und der Verfolgung der Knappheit biete ich die folgende mildere Richtlinie an:

Richtlinie für die Funktionsnotation in ES6:

  • Nicht benutzen this.
  • Verwenden Sie Funktionsdeklarationen für Funktionen, die Sie beim Namen aufrufen möchten (weil sie hochgezogen sind).
  • Verwenden Sie Pfeilfunktionen für Rückrufe (da diese tendenziell kürzer sind).
Jackson
quelle
Stimmen Sie zu 100% Ihrem Abschnitt "Richtlinie für die Funktionsnotation in ES6" unten zu - insbesondere bei Hub- und Inline-Rückruffunktionen. gute Antwort!
Jeff McCloud
1

Auf einfache Weise

var a =20; function a(){this.a=10; console.log(a);} 
//20, since the context here is window.

Eine andere Instanz:

var a = 20;
function ex(){
this.a = 10;
function inner(){
console.log(this.a); //can you guess the output of this line.
}
inner();
}
var test = new ex();

Antwort: Die Konsole würde 20 drucken.

Der Grund dafür ist, dass bei jeder Ausführung einer Funktion ein eigener Stapel erstellt wird. In diesem Beispiel wird die exFunktion mit dem newOperator ausgeführt, sodass ein Kontext erstellt wird. Wenn sie innerausgeführt wird, erstellt JS einen neuen Stapel und führt die innerFunktion a ausglobal context obwohl es eine gibt lokaler Kontext.

Wenn wir also möchten, dass die innerFunktion einen lokalen Kontext exhat, müssen wir den Kontext an die innere Funktion binden.

Pfeile lösen dieses Problem, anstatt die zu Global contextnehmen, local contextwenn sie welche haben . In der given example,wird es new ex()als dauernthis .

In allen Fällen, in denen die Bindung explizit ist, lösen Pfeile das Problem standardmäßig.

Rajendra Kumar Vankadari
quelle
1

Pfeilfunktionen oder Lambdas wurden in ES 6 eingeführt. Abgesehen von seiner Eleganz in minimaler Syntax besteht der bemerkenswerteste funktionale Unterschied darin, dass innerhalb einer Pfeilfunktion this ein Bereich festgelegt wird

In regulären Funktionsausdrücken ist das thisSchlüsselwort je nach Kontext, in dem es aufgerufen wird, an unterschiedliche Werte gebunden .

In Pfeil Funktionen , thisist lexikalisch gebunden, das heißt , es schließt sich über thisaus dem Bereich , in dem der Pfeil - Funktion definiert wurde (Eltern-Umfang), und ändert sich nicht , egal wo und wie sie aufgerufen wird / genannt.

Einschränkungen Pfeilfunktionen als Methoden für ein Objekt

// this = global Window
let objA = {
 id: 10,
 name: "Simar",
 print () { // same as print: function() 
  console.log(`[${this.id} -> ${this.name}]`);
 }
}
objA.print(); // logs: [10 -> Simar]
objA = {
 id: 10,
 name: "Simar",
 print: () => {
  // closes over this lexically (global Window)
  console.log(`[${this.id} -> ${this.name}]`);
 }
};
objA.print(); // logs: [undefined -> undefined]

Im Fall von , objA.print()wenn print()Methode regelmäßig definiert mit function , es funktionierte durch die Lösung thisrichtig objAfür Methodenaufruf aber nicht , wenn sie als Pfeil definiert =>Funktion. Dies liegt daran, dass thisin einer regulären Funktion beim Aufrufen als Methode für ein Objekt ( objA) das Objekt selbst vorhanden ist. Im Falle einer Pfeilfunktion thiswird sie jedoch lexikalisch an die thisdes umschließenden Bereichs gebunden, in dem sie definiert wurde (in unserem Fall global / Window), und bleibt während des Aufrufs als Methode unverändert objA.

Vorteile einer Pfeilfunktion gegenüber regulären Funktionen in Methode (n) eines Objekts, ABER nur dann, wenn thiserwartet wird, dass sie bei der Zeitdefinition festgelegt und gebunden wird.

/* this = global | Window (enclosing scope) */

let objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( function() {
    // invoked async, not bound to objB
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [undefined -> undefined]'
objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( () => {
    // closes over bind to this from objB.print()
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [20 -> Paul]

Im Fall des in objB.print()dem print()Verfahren als Funktion definiert , dass Invokes console.log([$ {this.id} -> {this.name}] )asynchron als Rückruf auf setTimeout , thisaufgelöst richtig , objBwenn ein Pfeil Funktion als Rückruf verwendet wurde , aber nicht wenn Rückruf als reguläre Funktion definiert wurde. Dies liegt daran =>, dass setTimeout(()=>..)die thisPfeilfunktion von ihrem übergeordneten Element lexikalisch an geschlossen übergeben wurde, d. H. Aufruf objB.print()davon definiert es. Mit anderen Worten-der Pfeil =>bestand Funktion um bis setTimeout(()==>...zu gebunden objBals , thisweil die in Aufruf objB.print() thiswar objBselbst.

Wir könnten leicht verwenden Function.prototype.bind(), um den Rückruf als reguläre Funktion zu definieren, indem wir ihn an die richtige binden this.

const objB = {
 id: 20,
 name: "Singh",
 print () { // same as print: function() 
  setTimeout( (function() {
    console.log(`[${this.id} -> ${this.name}]`);
  }).bind(this), 1)
 }
}
objB.print() // logs: [20 -> Singh]

Pfeilfunktionen sind jedoch praktisch und weniger fehleranfällig für den Fall von asynchronen Rückrufen, bei denen wir das kennen this zum Zeitpunkt der Funktionsdefinition abgerufen werden und gebunden werden sollten.

Einschränkung von Pfeilfunktionen, bei denen sich dies über Aufrufe hinweg ändern muss

Immer thiswenn wir eine Funktion benötigen , die zum Zeitpunkt des Aufrufs geändert werden kann, können wir keine Pfeilfunktionen verwenden.

/* this = global | Window (enclosing scope) */

function print() { 
   console.log(`[${this.id} -> {this.name}]`);
}
const obj1 = {
 id: 10,
 name: "Simar",
 print // same as print: print
};
obj.print(); // logs: [10 -> Simar]
const obj2 = {
 id: 20,
 name: "Paul",
};
printObj2 = obj2.bind(obj2);
printObj2(); // logs: [20 -> Paul]
print.call(obj2); // logs: [20 -> Paul]

Keine der oben genannten Funktionen funktioniert const print = () => { console.log(mit der Pfeilfunktion [$ {this.id} -> {this.name}], );}da thissie nicht geändert werden kann und an thisden umschließenden Bereich gebunden bleibt, in dem sie definiert wurde (global / Window). In all diesen Beispielen haben wir dieselbe Funktion mit verschiedenen Objekten ( obj1und obj2) nacheinander aufgerufen , die beide nach dem erstellt wurdenprint() Deklaration Funktion erstellt wurden.

Dies waren erfundene Beispiele, aber lassen Sie uns über einige weitere Beispiele aus dem wirklichen Leben nachdenken. Wenn wir unsere reduce()Methode ähnlich wie eine schreiben müssten , die funktioniert arrays , können wir sie wiederum nicht als Lambda definieren, da sie abgeleitet werden mussthis aus dem Aufrufkontext abgeleitet werden muss, d. H. das Array, auf dem es aufgerufen wurde

Aus diesem Grund können constructorFunktionen niemals als Pfeilfunktionen definiert werden, da thisfür eine Konstruktorfunktion zum Zeitpunkt ihrer Deklaration keine Funktionen festgelegt werden können. Jedes Mal, wenn eine Konstruktorfunktion mit aufgerufen wirdnew Schlüsselwort , wird ein neues Objekt erstellt, das dann an diesen bestimmten Aufruf gebunden wird.

Auch wenn Frameworks oder Systeme eine Rückruffunktion (en) akzeptieren, die später mit dynamischem Kontext aufgerufen werden sollen this , können wir keine Pfeilfunktionen verwenden, da diese thismöglicherweise bei jedem Aufruf erneut geändert werden müssen. Diese Situation tritt häufig bei DOM-Ereignishandlern auf

'use strict'
var button = document.getElementById('button');
button.addEventListener('click', function {
  // web-api invokes with this bound to current-target in DOM
  this.classList.toggle('on');
});
var button = document.getElementById('button');
button.addEventListener('click', () => {
  // TypeError; 'use strict' -> no global this
  this.classList.toggle('on');
});

Dies ist auch der Grund, warum in Frameworks wie Angular 2+ und Vue.js erwartet wird, dass die Bindungsmethoden für Vorlagenkomponenten reguläre Funktionen / Methoden sind, da thisihr Aufruf von den Frameworks für die Bindungsfunktionen verwaltet wird. (Angular verwendet Zone.js, um den asynchronen Kontext für Aufrufe von Bindungsfunktionen für Ansichtsvorlagen zu verwalten.)

Auf der anderen Seite, in Reaktion , wenn wir einer Komponente Methode als ein Ereignis-Handler zum Beispiel passieren wollen <input onChange={this.handleOnchange} />wir definieren sollten handleOnchanage = (event)=> {this.props.onInputChange(event.target.value);}als Pfeil Funktion wie bei jedem Aufruf wollen wir dieselbe Instanz der Komponente sein, der die JSX produziert für erbrachte DOM-Element.


Dieser Artikel ist auch in meiner Medium- Veröffentlichung verfügbar . Wenn Ihnen der Artikel gefällt oder Sie Kommentare und Vorschläge haben, klatschen Sie bitte oder hinterlassen Sie Kommentare zu Medium .

Simar Singh
quelle