CoffeeScript, Wann wird der Fettpfeil (=>) über dem Pfeil (->) verwendet und umgekehrt?

133

Sollten beim Erstellen einer Klasse in CoffeeScript alle Instanzmethoden mit dem =>Operator ("Fettpfeil") und alle statischen Methoden mit dem ->Operator definiert werden ?

Ali Salehi
quelle
Können Sie einen Beispielcode posten?
Sarnold
Siehe auch diese Antwort stackoverflow.com/a/17431824/517371
Tobia

Antworten:

157

Nein, das ist nicht die Regel, die ich verwenden würde.

Der Hauptanwendungsfall, den ich für den Fettpfeil beim Definieren von Methoden gefunden habe, ist, wenn Sie eine Methode als Rückruf verwenden möchten und diese Methode auf Instanzfelder verweist:

class A
  constructor: (@msg) ->
  thin: -> alert @msg
  fat:  => alert @msg

x = new A("yo")
x.thin() #alerts "yo"
x.fat()  #alerts "yo"

fn = (callback) -> callback()

fn(x.thin) #alerts "undefined"
fn(x.fat)  #alerts "yo"
fn(-> x.thin()) #alerts "yo"

Wie Sie sehen, können Probleme auftreten, wenn Sie einen Verweis auf die Methode einer Instanz als Rückruf übergeben, wenn Sie den Fettpfeil nicht verwenden. Dies liegt daran, dass der Fettpfeil die Instanz des Objekts an bindet, thiswährend der Dünnpfeil dies nicht tut. Daher können Dünnpfeilmethoden, die wie oben als Rückrufe bezeichnet werden, nicht auf die Felder der Instanz zugreifen @msgoder andere Instanzmethoden aufrufen. In der letzten Zeile gibt es eine Problemumgehung für Fälle, in denen der dünne Pfeil verwendet wurde.

nicolaskruchten
quelle
2
Was tun Sie, wenn Sie die thisvom dünnen Pfeil aufgerufenen verwenden möchten , aber auch die Instanzvariablen, die Sie mit dem fetten Pfeil erhalten würden?
Andrew Mao
Wie gesagt "In der letzten Zeile gibt es eine Problemumgehung für Fälle, in denen der dünne Pfeil verwendet wurde."
nicolaskruchten
Ich denke, Sie haben meine Frage falsch verstanden. Angenommen, der Standardbereich des Rückrufs wurde thisauf eine Variable festgelegt, die ich verwenden möchte. Ich möchte jedoch auch auf eine Klassenmethode thisverweisen , also möchte ich auch auf die Klasse verweisen. Ich kann nur zwischen einer Zuordnung wählen. thisWie kann ich also beide Variablen am besten verwenden?
Andrew Mao
@ AndrewMao Sie sollten wahrscheinlich eine vollständige Frage auf dieser Seite posten, anstatt mich in einem Kommentar beantworten zu lassen :)
nicolaskruchten
Es ist in Ordnung, Frage ist nicht so wichtig. Aber ich wollte nur klarstellen, dass es nicht das war, worauf Sie sich in Ihrer letzten Codezeile bezogen haben.
Andrew Mao
13

Ein Punkt, der in anderen Antworten nicht erwähnt wird und der wichtig ist, ist, dass Bindungsfunktionen mit Fettpfeil, wenn dies nicht erforderlich ist, zu unbeabsichtigten Ergebnissen führen können, wie in diesem Beispiel mit einer Klasse, die wir einfach DummyClass nennen.

class DummyClass
    constructor : () ->
    some_function : () ->
        return "some_function"

    other_function : () =>
        return "other_function"

dummy = new DummyClass()
dummy.some_function() == "some_function"     # true
dummy.other_function() == "other_function"   # true

In diesem Fall tun die Funktionen genau das, was man erwarten könnte, und es scheint keinen Verlust bei der Verwendung von Fettpfeilen zu geben. Was passiert jedoch, wenn wir den DummyClass-Prototyp ändern, nachdem er bereits definiert wurde (z. B. Ändern einer Warnung oder Ändern der Ausgabe eines Protokolls)? ::

DummyClass::some_function = ->
    return "some_new_function"

DummyClass::other_function = ->
    return "other_new_function"

dummy.some_function() == "some_new_function"   # true
dummy.other_function() == "other_new_function" # false
dummy.other_function() == "other_function"     # true

Wie wir sehen können, führt das Überschreiben unserer zuvor definierten Funktion des Prototyps dazu, dass some_function korrekt überschrieben wird, other_function jedoch in Instanzen gleich bleibt, da der fette Pfeil dazu geführt hat, dass other_function aus der Klasse an alle Instanzen gebunden wurde, sodass Instanzen nicht auf ihre Klasse zurückgreifen eine Funktion finden

DummyClass::other_function = =>
    return "new_other_new_function"

dummy.other_function() == "new_other_new_function"    # false

second_dummy = new DummyClass()
second_dummy.other_function() == "new_other_new_function"   # true

Selbst ein Fettpfeil funktioniert nicht, da ein Fettpfeil nur dazu führt, dass die Funktion an neue Instanzen gebunden wird (die die neuen Funktionen wie erwartet erhalten).

Dies führt jedoch zu einigen Problemen. Was ist, wenn wir eine Funktion benötigen (z. B. beim Umschalten einer Protokollierungsfunktion auf eine Ausgabebox oder ähnliches), die auf allen vorhandenen Instanzen (einschließlich Ereignishandlern) funktioniert [als solche, die wir nicht verwenden können? fette Pfeile in der ursprünglichen Definition], aber wir benötigen weiterhin Zugriff auf interne Attribute in einem Ereignishandler [der genaue Grund, warum wir fette Pfeile und keine dünnen Pfeile verwendet haben].

Der einfachste Weg, dies zu erreichen, besteht darin, lediglich zwei Funktionen in die ursprüngliche Klassendefinition aufzunehmen, eine mit einem dünnen Pfeil, der die Operationen ausführt, die Sie ausführen möchten, und eine andere, die mit einem fetten Pfeil definiert ist, der nichts anderes tut, als die erste Funktion aufzurufen beispielsweise:

class SomeClass
    constructor : () ->
        @data = 0
    _do_something : () ->
        return @data
    do_something : () =>
        @_do_something()

something = new SomeClass()
something.do_something() == 0     # true
event_handler = something.do_something
event_handler() == 0              # true

SomeClass::_do_something = -> return @data + 1

something.do_something() == 1     # true
event_handler() == 1              # true

Wann man also dünne / fette Pfeile verwendet, lässt sich auf vier Arten ziemlich einfach zusammenfassen:

  1. Funktionen für dünne Pfeile allein sollten verwendet werden, wenn beide Bedingungen erfüllt sind:

    • Die Methode wird niemals als Referenz übergeben, einschließlich event_handlers, z. B. haben Sie niemals einen Fall wie: some_reference = some_instance.some_method; some_reference ()
    • UND die Methode sollte für alle Instanzen universell sein. Wenn sich die Prototypfunktion ändert, gilt dies auch für alle Instanzen
  2. Fettpfeil-allein-Funktionen sollten verwendet werden, wenn die folgende Bedingung erfüllt ist:

    • Die Methode sollte bei der Instanzerstellung genau an die Instanz gebunden sein und dauerhaft gebunden bleiben, auch wenn sich die Funktionsdefinition für den Prototyp ändert. Dies schließt alle Fälle ein, in denen die Funktion ein Ereignishandler sein sollte und das Verhalten des Ereignishandlers konsistent sein sollte
  3. Die Fettpfeilfunktion, die direkt eine Dünnpfeilfunktion aufruft, sollte verwendet werden, wenn die folgenden Bedingungen erfüllt sind:

    • Die Methode muss als Referenz aufgerufen werden, z. B. als Ereignishandler
    • UND die Funktionalität kann sich in Zukunft ändern und vorhandene Instanzen beeinflussen, indem die Funktion für dünne Pfeile ersetzt wird
  4. Eine Dünnpfeilfunktion, die direkt eine Fettpfeilfunktion (nicht demonstriert) aufruft, sollte verwendet werden, wenn die folgenden Bedingungen erfüllt sind:

    • Die Fettpfeilfunktion muss immer an die Instanz angehängt werden
    • ABER die Dünnpfeilfunktion kann sich ändern (sogar zu einer neuen Funktion, die die ursprüngliche Fettpfeilfunktion nicht verwendet)
    • UND die Dünnpfeilfunktion muss niemals als Referenz übergeben werden

Bei allen Ansätzen muss in dem Fall berücksichtigt werden, dass die Prototypfunktionen möglicherweise geändert werden, ob sich das Verhalten für bestimmte Instanzen korrekt verhält oder nicht, obwohl eine Funktion mit einem fetten Pfeil definiert ist, ist ihr Verhalten innerhalb einer Instanz möglicherweise nicht konsistent, wenn sie aufgerufen wird Eine Methode, die innerhalb des Prototyps geändert wird

Jamesernator
quelle
9

Normalerweise ->ist in Ordnung.

class Foo
  @static:  -> this
  instance: -> this

alert Foo.static() == Foo # true

obj = new Foo()
alert obj.instance() == obj # true

Beachten Sie, wie die statische Methode das Klassenobjekt für thisund die Instanz das Instanzobjekt für zurückgibt this.

Was passiert ist, dass die Aufrufsyntax den Wert von bereitstellt this. In diesem Code:

foo.bar()

foowird bar()standardmäßig der Kontext der Funktion sein. Es funktioniert also irgendwie so, wie Sie es wollen. Sie benötigen den fetten Pfeil nur, wenn Sie diese Funktion auf eine andere Weise aufrufen, bei der die Punktsyntax nicht für den Aufruf verwendet wird.

# Pass in a function reference to be called later
# Then later, its called without the dot syntax, causing `this` to be lost
setTimeout foo.bar, 1000

# Breaking off a function reference will lose it's `this` too.
fn = foo.bar
fn()

In beiden Fällen würde die Verwendung eines fetten Pfeils zur Deklaration dieser Funktion es diesen ermöglichen, zu arbeiten. Aber wenn Sie nichts Seltsames tun, müssen Sie dies normalerweise nicht tun.

Verwenden ->Sie es also, bis Sie es wirklich brauchen, =>und verwenden Sie es niemals =>standardmäßig.

Alex Wayne
quelle
1
Dies wird fehlschlagen, wenn Sie tun:x = obj.instance; alert x() == obj # false!
nicolaskruchten
2
Natürlich wird es, aber das würde unter "falsch machen" fallen. Ich habe jetzt meine Antwort bearbeitet und erklärt, wann a =>für die statischen / Instanzmethoden einer Klasse benötigt wird.
Alex Wayne
Nitpick: // is not a CoffeeScript commentwährend # is a CoffeeScript comment.
nicolaskruchten
Wie ist setTimeout foo.bar, 1000"falsch machen"? Die Verwendung eines Fettpfeils ist viel schöner als die Verwendung von setTimeout (-> foo.bar()), 1000IMHO.
nicolaskruchten
1
@nicolaskruchten Es gibt natürlich einen Fall für diese Syntax in setTimeout. Aber Ihr erster Kommentar ist etwas erfunden und enthüllt keinen legitimen Anwendungsfall, sondern zeigt einfach, wie er brechen könnte. Ich sage nur, dass Sie a nicht verwenden sollten, es sei =>denn, Sie benötigen es aus einem guten Grund, insbesondere bei Klasseninstanzmethoden, bei denen die Leistungskosten für die Erstellung einer neuen Funktion an die Instanziierung gebunden sind.
Alex Wayne
5

Nur ein Beispiel, um den fetten Pfeil zu verstehen

funktioniert nicht: (@canvas undefined)

class Test
  constructor: ->
    @canvas = document.createElement 'canvas'
    window.addEventListener 'resize', ->
      @canvas.width = window.innerWidth
      @canvas.height = window.innerHeight

funktioniert: (@canvas definiert)

class Test
  constructor: ->
    @canvas = document.createElement 'canvas'
    window.addEventListener 'resize', =>
      @canvas.width = window.innerWidth
      @canvas.height = window.innerHeight
user3896501
quelle