Sollten beim Erstellen einer Klasse in CoffeeScript alle Instanzmethoden mit dem =>
Operator ("Fettpfeil") und alle statischen Methoden mit dem ->
Operator definiert werden ?
Sollten beim Erstellen einer Klasse in CoffeeScript alle Instanzmethoden mit dem =>
Operator ("Fettpfeil") und alle statischen Methoden mit dem ->
Operator definiert werden ?
Antworten:
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, this
wä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 @msg
oder andere Instanzmethoden aufrufen. In der letzten Zeile gibt es eine Problemumgehung für Fälle, in denen der dünne Pfeil verwendet wurde.
this
vom dünnen Pfeil aufgerufenen verwenden möchten , aber auch die Instanzvariablen, die Sie mit dem fetten Pfeil erhalten würden?
this
auf eine Variable festgelegt, die ich verwenden möchte. Ich möchte jedoch auch auf eine Klassenmethode this
verweisen , also möchte ich auch auf die Klasse verweisen. Ich kann nur zwischen einer Zuordnung wählen. this
Wie kann ich also beide Variablen am besten verwenden?
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:
Funktionen für dünne Pfeile allein sollten verwendet werden, wenn beide Bedingungen erfüllt sind:
Fettpfeil-allein-Funktionen sollten verwendet werden, wenn die folgende Bedingung erfüllt ist:
Die Fettpfeilfunktion, die direkt eine Dünnpfeilfunktion aufruft, sollte verwendet werden, wenn die folgenden Bedingungen erfüllt sind:
Eine Dünnpfeilfunktion, die direkt eine Fettpfeilfunktion (nicht demonstriert) aufruft, sollte verwendet werden, wenn die folgenden Bedingungen erfüllt sind:
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
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 this
und 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()
foo
wird 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.
x = obj.instance; alert x() == obj # false!
=>
für die statischen / Instanzmethoden einer Klasse benötigt wird.
// is not a CoffeeScript comment
während # is a CoffeeScript comment
.
setTimeout foo.bar, 1000
"falsch machen"? Die Verwendung eines Fettpfeils ist viel schöner als die Verwendung von setTimeout (-> foo.bar()), 1000
IMHO.
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.
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