it-swarm.com.de

Wann sollte ich Pfeilfunktionen in ECMAScript 6 verwenden?

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

Mit () => {} Und function () {} erhalten wir zwei sehr ähnliche Möglichkeiten, Funktionen in ES6 zu schreiben. In anderen Sprachen zeichnen sich Lambda-Funktionen oft dadurch aus, dass sie anonym sind. In ECMAScript kann jedoch jede Funktion anonym sein. Jeder der beiden Typen verfügt über eindeutige Verwendungsdomänen (dh, wenn this 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 Schreibweisen zutrifft.

Pfeilfunktionen in ES6 haben mindestens zwei Einschränkungen:

  • Arbeite nicht mit new
  • Behoben, dass this bei der Initialisierung an den Gültigkeitsbereich gebunden war

Abgesehen von diesen beiden Einschränkungen könnten Pfeilfunktionen die regulären 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", d. h. überall, wo eine Funktion nicht unabhängig von der Variablen this sein muss und wir kein Objekt erstellen.
  • nur "überall, wo sie benötigt werden", d. h. Ereignis-Listener, Zeitüberschreitungen, die an einen bestimmten Bereich gebunden werden müssen
  • mit 'kurzen' Funktionen, aber nicht mit 'langen' Funktionen
  • nur bei 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 vermittelt werden kann, und konsistent sein, damit sie nicht ständig von einer Funktionsnotation zur nächsten hin und her überarbeitet werden muss.

372
lyschoening

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

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

Warum fast überall Pfeilfunktionen verwenden?

  1. Sicherheit des Gültigkeitsbereichs: Wenn Pfeilfunktionen konsistent verwendet werden, wird garantiert, dass für alle Elemente dasselbe thisObject wie für das Stammverzeichnis verwendet wird. Wenn sogar ein einzelner Standardfunktionsrückruf mit einer Reihe von Pfeilfunktionen gemischt wird, besteht die Möglichkeit, dass der Gültigkeitsbereich durcheinander gerät.
  2. Kompaktheit: Pfeilfunktionen sind leichter zu lesen und zu schreiben. (Dies scheint mir eine Meinung zu sein, deshalb werde ich weiter unten einige Beispiele anführen.).
  3. Klarheit: Wenn fast alles eine Pfeilfunktion ist, sticht jedes reguläre function sofort heraus, um den Bereich zu definieren. Ein Entwickler kann immer die nächsthöhere function Anweisung nachschlagen, um zu sehen, was die thisObject ist.

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

  1. Um eine Funktion anzugeben, die nicht auf thisObject zugreifen soll.
  2. Das Objekt window (globaler Bereich) wird am besten explizit angesprochen.
  3. Viele Object.prototype - Definitionen existieren im globalen Bereich (denken Sie an String.prototype.truncate Usw.) und diese müssen im Allgemeinen ohnehin vom Typ function sein. Durch die konsequente Verwendung von function im globalen Bereich werden Fehler vermieden.
  4. Viele Funktionen im globalen Bereich sind Objektkonstruktoren für Klassendefinitionen im alten Stil.
  5. Funktionen können benannt werden1. Dies hat zwei Vorteile: (1) Es ist weniger umständlich, function foo(){} als const foo = () => {} zu schreiben - 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 sind hoisted (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 ist daher, dass sie doppelt so viele Funktionen wie Objektkonstruktoren haben:

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

Die Funktionsweise ist jedoch identisch2 ES Harmony Entwurf einer Klassendefinition ist fast so kompakt:

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

Ich gehe davon aus, dass die Verwendung der früheren Notation möglicherweise nicht mehr empfohlen wird. Die Objektkonstruktornotation kann von einigen weiterhin für einfache anonyme Objektfactorys verwendet werden, in denen Objekte programmgesteuert generiert werden, für andere jedoch nicht.

Wo ein Objektkonstruktor benötigt wird, sollte man erwägen, die Funktion wie oben gezeigt in ein class zu konvertieren. Die Syntax funktioniert auch mit anonymen Funktionen/Klassen.


Lesbarkeit von Pfeilfunktionen

Das wahrscheinlich beste Argument für das Festhalten an regulären Funktionen - Umfangssicherheit sei verdammt - wäre, dass Pfeilfunktionen weniger lesbar sind als reguläre Funktionen. Wenn Ihr Code überhaupt nicht funktioniert, erscheinen Pfeilfunktionen möglicherweise nicht erforderlich, und wenn Pfeilfunktionen nicht konsistent verwendet werden, sehen sie hässlich aus.

ECMAScript hat sich ziemlich verändert, seit ECMAScript 5.1 uns die Funktionen Array.forEach, Array.map Und all diese Funktionen zur funktionalen Programmierung gegeben hat, mit denen wir Funktionen verwendet haben, bei denen zuvor for-Schleifen verwendet worden wären. Asynchrones JavaScript hat einiges abgenommen. ES6 wird auch ein Promise Objekt ausliefern, was noch mehr anonyme Funktionen bedeutet. Es gibt kein Zurück für die funktionale Programmierung. In funktionalem JavaScript sind Pfeilfunktionen regulären Funktionen vorzuziehen.

Nehmen Sie zum Beispiel 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;
        })
}

Das gleiche Stück 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));
}

Zwar kann eine der Pfeilfunktionen durch eine Standardfunktion ersetzt werden, es wäre jedoch sehr wenig davon zu profitieren. Welche Version ist besser lesbar? Ich würde das erste sagen.

Ich denke, die Frage, ob Pfeilfunktionen oder reguläre Funktionen verwendet werden sollen, wird mit der Zeit an Relevanz verlieren. Die meisten Funktionen werden entweder zu Klassenmethoden, die das Schlüsselwort function ersetzen, oder zu Klassen. Funktionen zum Patchen von Klassen über Object.prototype Bleiben erhalten. In der Zwischenzeit schlage ich vor, das Schlüsselwort function für alles zu reservieren, was wirklich eine Klassenmethode oder eine Klasse sein sollte.


Notizen

  1. Benannte Pfeilfunktionen wurden in der ES6-Spezifikation zurückgestellt . Möglicherweise wird noch eine zukünftige Version hinzugefügt.
  2. Gemäß dem Spezifikationsentwurf "Klassendeklarationen/-ausdrücke erstellen ein Konstruktorfunktions-/Prototyppaar genau wie für Funktionsdeklarationen" solange eine Klasse das Schlüsselwort extend 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 immer dann, wenn eine Pfeilfunktion nur für den Nebeneffekt aufgerufen wird (z. B. Zuweisung). Auf diese Weise ist klar, dass der Rückgabewert verworfen werden kann.
303
lyschoening

Gemäß Vorschlag zielten die Pfeile darauf ab, "mehrere häufig auftretende Schwachstellen des traditionellen Function Expression Anzugehen und zu lösen". Sie wollten die Sache verbessern, indem sie this lexikalisch banden und knappe Syntax anboten.

Jedoch,

  • Man kann this nicht konsequent lexikalisch binden
  • Die Pfeilfunktionssyntax ist heikel und mehrdeutig

Daher sorgen Pfeilfunktionen für Verwirrung und Fehler und sollten aus dem Wortschatz eines JavaScript-Programmierers ausgeschlossen werden, der ausschließlich durch function ersetzt wird.

Bezüglich lexikalischer 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);
};

Pfeilfunktionen sollen das Problem beheben, bei dem innerhalb eines Rückrufs auf eine Eigenschaft von this zugegriffen werden muss. Dafür gibt es bereits mehrere Möglichkeiten: Sie können einer Variablen this zuweisen, bind verwenden oder das dritte Argument verwenden, das für die Aggregatmethoden Array verfügbar ist. Dennoch scheinen Pfeile 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 this speziell binden. Nun gibt es zwei Werte für this:

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

Wir müssen function verwenden, damit eachthis dynamisch bindet. Wir können hier keine Pfeilfunktion verwenden.

Der Umgang mit mehreren this Werten kann auch verwirrend sein, da schwer zu erkennen ist, über welches this ein Autor gesprochen hat:

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

Wollte der Autor tatsächlich Book.prototype.reformat Aufrufen? Oder hat er vergessen, this zu binden und Reader.prototype.reformat Aufzurufen? Wenn wir den Handler in eine Pfeilfunktion ändern, werden wir uns ebenfalls fragen, ob der Autor den dynamischen this wollte, aber dennoch einen Pfeil ausgewä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 this Werte 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 'Edge-Fall' hervorgerufen wurde?" 100% der Zeit.

Es gibt einen besseren Weg: Verwenden Sie immer function (damit this immer dynamisch gebunden werden kann) und referenzieren Sie this immer über eine Variable. Variablen sind lexikalisch und nehmen viele Namen an. Wenn Sie einer Variablen this zuweisen, werden Ihre Absichten deutlich:

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

Darüber hinaus stellt always das Zuweisen von this zu einer Variablen (auch wenn es eine einzelne this oder keine anderen Funktionen gibt) sicher, dass die eigenen Absichten auch nach dem Code klar bleiben geändert.

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

  • Mocha (~ 120k Downloads gestern) stellt Methoden für seine Tests über this zur Verfügung.
  • Grunt (~ 63k Downloads gestern) enthüllt Methoden für Build-Tasks über this.
  • Backbone (~ 22.000 Downloads gestern) definiert Methoden für den Zugriff auf this.
  • Ereignis-APIs (wie die DOMs) verweisen auf ein EventTarget mit this.
  • Gepatchte oder erweiterte prototypische APIs verweisen auf Instanzen mit this.

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

Möglicherweise benötigen Sie bereits dynamische this Bindungen.

Ein lexikalisches this wird manchmal erwartet, aber manchmal nicht. genauso wie ein dynamisches this manchmal erwartet wird, aber manchmal nicht. Zum Glück gibt es einen besseren Weg, der immer die erwartete Bindung produziert und kommuniziert.

Bezüglich der knappen Syntax

Pfeilfunktionen konnten eine "kürzere syntaktische Form" für Funktionen bereitstellen. Aber werden Sie diese kürzeren Funktionen erfolgreicher machen?

Ist x => x * x "Leichter zu lesen" als function (x) { return x * x; }? Vielleicht liegt es daran, dass es wahrscheinlicher ist, eine einzelne, kurze Codezeile zu produzieren. Nach Dyson Der 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 schneller 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 if -Anweisungen gegeben.

Sind Sie jedoch wirklich schreibend die einfachen mathematischen Funktionen im Vorschlag beworben ? Meine Domains sind nicht mathematisch, deshalb sind meine Unterprogramme selten so elegant. Vielmehr wird häufig festgestellt, dass Pfeilfunktionen eine Spaltenbegrenzung aufheben und aufgrund des Editors oder der Formatvorlage in eine andere Zeile umbrechen, wodurch die "Lesbarkeit" nach Dysons Definition aufgehoben wird.

Man könnte sich fragen: "Wie wäre es, wenn Sie nur die Kurzversion für kurze Funktionen verwenden, wenn dies möglich ist?" Aber jetzt widerspricht eine Stilregel einer Sprachbeschränkung: "Versuchen Sie, die kürzestmögliche Funktionsnotation zu verwenden, wobei zu berücksichtigen ist, dass manchmal nur die längste Notation this wie erwartet bindet." 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 doSomethingElse(x); befindet sich nicht im Hauptteil von b, sondern ist nur eine schlecht eingerückte Anweisung der obersten Ebene.

Beim Erweitern auf die Blockform gibt es kein implizites return mehr, das man vergessen könnte, wiederherzustellen. Aber der Ausdruck könnte only eine Nebenwirkung haben, also wer weiß, ob in Zukunft ein expliziter return notwendig 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 Rest-Parameter gedacht sein kann, kann als Spread-Operator analysiert werden:

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

Die Zuweisung kann mit den 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 erstellen oder eine Funktion, die ein leeres Objekt zurückgibt? (Sollten wir in diesem Sinne jemals { Nach => Setzen? Sollen wir uns nur auf die Ausdruckssyntax beschränken? Das 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 außen () Und innen () Platziert werden. Dies ist gültig und kann beabsichtigt sein.

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

Obwohl, wenn man (() => doSomething()()); Mit der Absicht schreibt, einen sofort aufgerufenen Funktionsausdruck zu schreiben, passiert einfach nichts.

Es ist schwer zu argumentieren, dass Pfeilfunktionen in Anbetracht der oben genannten Fälle "verständlicher" sind. Man lernt could alle Sonderregeln, die zur Verwendung dieser Syntax erforderlich sind. Lohnt es sich wirklich?

Die Syntax von function ist ausnahmsweise verallgemeinert. Die ausschließliche Verwendung von function bedeutet, dass die Sprache selbst verhindert, dass man verwirrenden Code schreibt. Um Prozeduren zu schreiben, die in allen Fällen syntaktisch verstanden werden sollten, wähle ich function.

Zu einer 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:

Leitfaden zur Funktionsnotation in ES6:

  • Erstellen Sie Prozeduren immer mit function.
  • Ordnen Sie einer Variablen immer this zu. Verwenden Sie nicht () => {}.
79
Jackson

Pfeilfunktionen wurden erstellt, um die Funktion scope zu vereinfachen und das Schlüsselwort this durch Vereinfachung zu lösen. Sie verwenden die Syntax =>, Die wie ein Pfeil aussieht.

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

Werfen wir einen Blick auf die vorhandene ES5-Syntax. Wenn das Schlüsselwort this in der Methode eines Objekts (einer Funktion, die zu einem Objekt gehört) enthalten wäre, worauf würde es sich beziehen?

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

Das obige Snippet verweist auf ein object und gibt den Namen "RajiniKanth" Aus. Sehen wir uns den folgenden Ausschnitt an 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 das Schlüsselwort this in method’s function Enthalten ist?

Hier würde dies auf window object Als auf inner function Verweisen, da es aus scope herausgefallen ist. Da this immer auf den Eigentümer der Funktion verweist, in der es sich befindet, ist in diesem Fall - da sie jetzt außerhalb des Gültigkeitsbereichs liegt - das Fenster/globale Objekt.

Wenn es sich innerhalb der Methode eines object befindet, ist der Eigentümer des function das Objekt. Somit ist das Schlüsselwort this an das Objekt gebunden. Befindet es sich jedoch innerhalb einer Funktion, sei es eigenständig oder innerhalb einer anderen Methode, wird immer auf das Objekt window/global Verwiesen.

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

fn(); // [object Window]

Es gibt Möglichkeiten, dieses Problem in unserem ES5 Selbst zu lösen. Sehen wir uns das an, bevor wir in die Pfeilfunktionen von ES6 eintauchen, um herauszufinden, wie es gelöst wird.

Normalerweise erstellen Sie eine Variable außerhalb der inneren Funktion der Methode. Jetzt erhält die Methode ‘forEach’ Zugriff auf this und damit auf die Eigenschaften object’s 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 this - Schlüsselwort, das sich auf die Methode bezieht, an method’s inner function anzuhängen.

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 Pfeilfunktion ES6 Können wir das Problem lexical scoping 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 functions Sind eher Funktionsanweisungen, außer dass sie bind das dies zu parent scope Machen. Wenn das Argument arrow function is in top scope, this auf window/global scope Verweist, hat eine Pfeilfunktion innerhalb einer regulären Funktion dasselbe Argument wie ihre äußere Funktion.

Mit arrow Funktionen this wird zum Erstellungszeitpunkt an das umschließende scope gebunden und kann nicht geändert werden. Der neue Operator "Binden", "Aufrufen" und "Anwenden" haben keine Auswirkungen 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 Variablenreferenz von this oder bind verwenden. Mit ES6 wird es einfacher, this als gebunden an lexical scoping Zu verwalten.

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 Pfeil nicht funktioniert

In einem Objektliteral.

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

Actor.getName();

Actor.getName Wird mit einer Pfeilfunktion definiert, warnt jedoch beim Aufruf undefiniert, da this.nameundefined ist, da der Kontext für window verbleibt.

Dies geschieht, weil die Pfeilfunktion den Kontext lexikalisch mit dem window object Verknüpft, d. H. Dem äußeren Gültigkeitsbereich. Das Ausführen von this.name Entspricht window.name, Was nicht definiert ist.

Objektprototyp

Dieselbe Regel gilt für die Definition von Methoden auf einem prototype object. Anstatt eine Pfeilfunktion zum Definieren der sayCatName-Methode zu verwenden, die einen falschen context window Liefert:

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

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

this wird aus dem umschließenden Kontext eingerichtet, d. h. aus dem äußeren Bereich, der dazu führt, dass es neu erstellten Objekten nicht zugewiesen wird.

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 Pfeil-Funktion bindet das context statisch bei der Deklaration und kann es nicht dynamisch machen. 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';
});

this ist ein Fenster in einer Pfeilfunktion, die im globalen Kontext definiert ist. Wenn ein Klickereignis eintritt, versucht der Browser, die Handlerfunktion mit dem Tastenkontext aufzurufen, aber die Pfeilfunktion ändert den vordefinierten Kontext nicht. this.innerHTML Entspricht window.innerHTML Und hat keinen Sinn.

Sie müssen einen Funktionsausdruck anwenden, der es ermöglicht, dies abhängig vom Zielelement zu ändern:

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. Daher ändert this.innerHTML = 'Clicked button' Den Schaltflächentext korrekt, um den Klickstatus wiederzugeben.

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

34
Thalaivar

Pfeilfunktionen - die bisher am häufigsten verwendete ES6-Funktion ...

Verwendung: Alle ES5-Funktionen sollten mit Ausnahme der folgenden Szenarien durch ES6-Pfeilfunktionen ersetzt werden:

Pfeilfunktionen sollten NICHT verwendet werden:

  1. Wenn wir Funktionsaufzug wollen
    • als pfeil funktionen sind anonym.
  2. Wenn wir this/arguments in einer Funktion Verwenden möchten
    • da Pfeilfunktionen keine eigenen this/arguments haben, hängen sie von ihrem äußeren Kontext ab.
  3. Wenn wir eine benannte Funktion verwenden wollen
    • als pfeil funktionen sind anonym.
  4. Wenn wir die Funktion als constructor Verwenden wollen
    • als Pfeil haben Funktionen kein eigenes this.
  5. Wenn wir eine Funktion als Eigenschaft in ein Objektliteral einfügen und ein Objekt darin verwenden möchten
    • da wir nicht auf this zugreifen können (was Objekt selbst sein sollte).

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

Variante 1: Wenn wir mehr als ein Argument an eine Funktion übergeben und einen Wert von dieser 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 Schlüsselwort function ist NICHT erforderlich. => Ist erforderlich. {} Ist optional, wenn wir nicht {} Angeben. return wird implizit von JavaScript hinzugefügt, und wenn wir {} Angeben, müssen wir return wenn wir es brauchen.

Variante 2: Wenn wir NUR ein Argument an eine Funktion übergeben und einen Wert von dieser 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 die Klammer () Weglassen.

Variante: Wenn wir KEIN Argument an eine Funktion übergeben und KEIN 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 wollen.

ES6-Version :

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

Variante 5: Wenn wir ein Objekt von Pfeilfunktionen zurückgeben wollen.

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 eigenes arguments (ein Array-ähnliches Objekt), sie hängen vom äußeren Kontext für arguments ab.

ES6-Version :

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

Hinweis: foo ist eine ES5-Funktion, an die ein arguments -arrayähnliches Objekt übergeben wird. Ein Argument lautet 2, Also arguments[0] Für foo ist 2.

abc ist eine ES6-Pfeilfunktion, da sie KEINE eigene arguments hat. Daher gibt sie stattdessen arguments[0] von foo ihrem äußeren Kontext aus.

Variante 7: Pfeilfunktionen haben KEIN eigenes this, sie hängen vom äußeren Kontext für this ab

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 Callback ist eine ES5-Funktion und hat ein eigenes this, das in der use-strict - Umgebung nicht definiert ist. Daher erhalten wir die 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: Der an setTimeout übergebene Rückruf ist eine ES6-Pfeilfunktion und hat KEINEN eigenen this, sodass er aus dem äußeren Kontext stammt, der greetUser ist hat this das ist obj6 daher erhalten wir die Ausgabe:

Hi, Welcome: Katty

Miscellaneous: Wir können new nicht mit Pfeilfunktionen verwenden. Pfeilfunktionen haben keine prototype -Eigenschaft. Wir haben keine Bindung von this, wenn die Pfeilfunktion über apply oder call aufgerufen wird.

14
Manishz90

Neben den bisher tollen Antworten möchte ich einen ganz anderen Grund vorstellen, warum Pfeilfunktionen in gewissem Sinne grundsätzlich besser sind als "gewöhnliche" JavaScript-Funktionen. Nehmen wir zur Erörterung vorübergehend an, wir verwenden einen Type Checker wie TypeScript oder Facebooks "Flow". Betrachten Sie das folgende Spielzeugmodul, das aus gültigem ECMAScript 6-Code und Anmerkungen zum Flusstyp besteht: (Ich werde den untypisierten Code, der sich realistisch aus Babel ergeben 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 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, die Typprüfung ist fehlgeschlagen hier: f2 sollte eine Zahl zurückgeben, aber es gab eine Zeichenkette zurück!

Schlimmer noch, es scheint, dass kein vorstellbarer Typprüfer normale (Nicht-Pfeil-) JavaScript-Funktionen verarbeiten kann, da das "dies" von f2 nicht in der Argumentliste von f2 vorkommt, also der erforderliche Typ für " dies "konnte unmöglich als annotation zu f2 hinzugefügt werden.

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

Hier ist die lauffähige untypisierte Version, die von Babel hergestellt werden 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!
6

Ich bevorzuge die Verwendung von Pfeilfunktionen zu allen Zeiten, in denen kein Zugriff auf local this erforderlich ist, da die Pfeilfunktion binden Sie nicht ihre eigenen this, arguments, super oder new.target .

3
zowers

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

Bezüglich lexikalischer this

In meiner letzten Antwort habe ich bewusst einen Grundgedanken, den ich in Bezug auf diese Sprache habe, vermieden, da er nicht direkt mit dem Argument zusammenhängt, das ich vorbrachte. Trotzdem kann ich verstehen, warum viele Leute meiner Empfehlung, keine Pfeile zu verwenden, einfach widersprechen, wenn sie Pfeile für so nützlich halten.

Ich bin der Meinung, dass wir this nicht verwenden sollten. Wenn eine Person die Verwendung von this in ihrem Code absichtlich vermeidet, ist das Merkmal "lexikalisch this" von Pfeilen daher von geringem bis keinem Wert. Unter der Voraussetzung, dass this eine schlechte Sache ist, ist die Behandlung von this durch Arrow weniger eine „gute Sache“, sondern eher eine Art Schadensbegrenzung für eine andere schlechte Sprachfunktion .

Ich gehe davon aus, dass dies einigen Leuten nicht einfällt, aber selbst denen, denen es einfällt, müssen sie sich immer in Codebasen wiederfinden, in denen this hundert Mal pro Datei vorkommt und ein wenig (oder viel) ) der Schadensbegrenzung ist alles, worauf eine vernünftige Person hoffen kann. Pfeile können also in gewisser Weise gut sein, wenn sie eine schlechte Situation verbessern.

Auch wenn es einfacher ist, Code mit this mit Pfeilen zu schreiben als ohne Pfeile, 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, sind sie meines Erachtens trotzdem zuckend und akzeptieren sie, weil der Wert von lexikalischem this sie in den Schatten stellt.

All dies ist ein Vorwort zu der folgenden Erkenntnis: Wenn man this nicht verwendet, wird die Mehrdeutigkeit über this, die Pfeile normalerweise verursachen, irrelevant. Pfeile werden in diesem Zusammenhang neutraler.

Bezüglich der knappen Syntax

Als ich meine erste Antwort schrieb, war ich der Meinung, dass selbst das Festhalten an Best Practices einen lohnenden Preis darstellt, wenn es 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 zu rechtfertigen, dass man manchmal von Best Practices abweicht.

Mit anderen Worten: Verdammt, ich möchte auch Einzeiler-Funktionen!

Zu einer Richtlinie

Da die Möglichkeit besteht, dass this -neutrale Pfeilfunktionen und Verspannungen angestrebt werden sollten, biete ich die folgende mildere Richtlinie an:

Leitfaden zur Funktionsnotation in ES6:

  • Verwenden Sie nicht this.
  • Verwenden Sie Funktionsdeklarationen für Funktionen, die Sie mit Namen aufrufen möchten (weil sie gehisst werden).
  • Verwenden Sie Pfeilfunktionen für Rückrufe (da diese tendenziell knapper sind).
2
Jackson

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 ausgeben.

Der Grund dafür ist, dass bei jeder Ausführung einer Funktion ein eigener Stapel erstellt wird. In diesem Beispiel wird die Funktion ex mit dem Operator new ausgeführt, sodass ein Kontext erstellt wird. Wenn inner ausgeführt wird, erstellt JS einen neuen Stack und führt die Funktion inner a global context aus, obwohl es einen lokalen Kontext gibt.

Wenn wir also wollen, dass die Funktion inner einen lokalen Kontext hat, der ex ist, müssen wir den Kontext an die innere Funktion binden.

Pfeile lösen dieses Problem, anstatt den Global context Zu nehmen, nehmen sie den local context, Falls vorhanden. Im given example, Wird new ex() als this verwendet.

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

Pfeilfunktionen oder Lambdas wurden in ES 6 eingeführt. Abgesehen von ihrer Eleganz in minimaler Syntax besteht der bemerkenswerteste funktionale Unterschied im Umfang von this innerhalb einer Pfeilfunktion

In regulären Funktionsausdrücken ist das Schlüsselwort this an verschiedene Werte gebunden, basierend auf dem Kontext, in dem es aufgerufen wird.

In Pfeilfunktionen ist this lexikalisch gebunden, dh, es wird über this aus dem Bereich geschlossen, in dem der Pfeil funktioniert wurde definiert (Parent-Scope) und ändert sich nicht, egal wo und wie es aufgerufen wird.

Einschränkungen Pfeil-Funktionen 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() mit der regulären function-Methode definiert wurde, wurde this ordnungsgemäß in objA für den Methodenaufruf aufgelöst, was jedoch fehlgeschlagen ist, wenn es als Pfeilfunktion => Definiert wurde. Dies liegt daran, dass this in einer regulären Funktion, wenn es als Methode für ein Objekt (objA) aufgerufen wird, das Objekt selbst ist. Bei einer Pfeilfunktion wird this jedoch lexikalisch an das this des einschließenden Bereichs gebunden, in dem es definiert wurde (in unserem Fall global/Window), und bleibt während des Aufrufs als Methode für objA unverändert.

Vorteile einer Pfeilfunktion gegenüber regulären Funktionen in Methode (n) eines Objekts, ABER nur, wenn erwartet wird, dass this 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 von objB.print(), wobei print() als Funktion definiert ist, die console.log( [$ {This.id} -> {this.name}] ) Asynchron als Rückruf für setTimeout aufruft, this wurde korrekt in objB aufgelöst, wenn eine Pfeilfunktion als Rückruf verwendet wurde, aber fehlgeschlagen ist, wenn der Rückruf als reguläre Funktion definiert wurde. Dies liegt daran, dass die Pfeilfunktion =>, Die an setTimeout(()=>..) übergeben wurde, über this lexikalisch von ihrem übergeordneten Element, d. H. Aufruf von objB.print(), der es definiert hat. Mit anderen Worten, die Pfeilfunktion => Wurde an setTimeout(()==>... übergeben und an objB als this gebunden, da der Aufruf von objB.print()this selbst objB war.

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

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 zum Zeitpunkt der Funktionsdefinition, an die sie gebunden werden sollen, die this kennen.

Einschränkung der Pfeilfunktionen, wenn diese sich bei verschiedenen Aufrufen ändern müssen

Immer wenn wir eine Funktion benötigen, deren this 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 mit der Pfeilfunktion const print = () => { console.log( [$ {this.id} -> {this.name}] );}, Da this nicht geändert werden kann und an die this des einschließenden Bereichs gebunden bleibt, in dem es wurde definiert (global/window). In all diesen Beispielen haben wir dieselbe Funktion mit verschiedenen Objekten (obj1 Und obj2) Nacheinander aufgerufen, die beide erstellt wurden, nachdem die Funktion print() deklariert wurde.

Dies waren erfundene Beispiele, aber lassen Sie uns über einige Beispiele aus dem wirklichen Leben nachdenken. Wenn wir unsere reduce() -Methode schreiben müssten, die derjenigen für arrays ähnelt, können wir sie erneut nicht als Lambda definieren, da sie this aus dem Aufrufkontext ableiten muss, d. H. das Array, auf dem es aufgerufen wurde

Aus diesem Grund können constructor-Funktionen niemals als Pfeilfunktionen definiert werden, da this für eine Konstruktorfunktion zum Zeitpunkt ihrer Deklaration nicht festgelegt werden kann. Jedes Mal, wenn eine Konstruktorfunktion mit dem Schlüsselwort new aufgerufen wird, wird ein neues Objekt erstellt, das dann an diesen bestimmten Aufruf gebunden wird.

Auch wenn Frameworks oder Systeme Callback-Funktionen akzeptieren, die später mit dem dynamischen Kontext this aufgerufen werden sollen, können wir Pfeilfunktionen nicht wie bisher verwenden. this muss möglicherweise bei jedem Aufruf geändert werden. 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 Erwarten Sie, dass die Bindungsmethoden der Schablonenkomponenten reguläre Funktionen/Methoden sind, da this für ihren Aufruf von den Frameworks für die Bindungsfunktionen verwaltet wird. (Angular verwaltet mithilfe von Zone.js den asynchronen Kontext für Aufrufe von Bindungsfunktionen für Ansichtsvorlagen.).

Auf der anderen Seite sollten wir in React , wenn wir die Methode einer Komponente als Event-Handler übergeben möchten, zum Beispiel <input onChange={this.handleOnchange} />, handleOnchanage = (event)=> {this.props.onInputChange(event.target.value);} definieren. Als Pfeilfunktion soll dies für jeden Aufruf dieselbe Instanz der Komponente sein, die das JSX für das gerenderte DOM-Element erzeugt hat.


Dieser Artikel ist auch in meiner Medium Publikation verfügbar. Wenn Sie den Artikel mögen oder irgendwelche Kommentare und Vorschläge haben, bitte klatschen oder lassen Sie Kommentare am Mittel .

1
Simar Singh