CoffeeScript, Quand utiliser la grosse flèche (=>) sur la flèche (->) et vice versa

133

Lors de la construction d'une classe dans CoffeeScript, toute la méthode d'instance doit-elle =>être définie à l'aide de l' ->opérateur ("grosse flèche") et toutes les méthodes statiques définies à l'aide de l' opérateur?

Ali Salehi
la source
Pouvez-vous publier un exemple de code?
sarnold
Voir aussi cette réponse stackoverflow.com/a/17431824/517371
Tobia

Réponses:

157

Non, ce n'est pas la règle que j'utiliserais.

Le cas d'utilisation principal que j'ai trouvé pour la grosse flèche dans la définition de méthodes est lorsque vous souhaitez utiliser une méthode comme rappel et que cette méthode fait référence aux champs d'instance:

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"

Comme vous le voyez, vous pouvez rencontrer des problèmes en passant une référence à la méthode d'une instance en tant que rappel si vous n'utilisez pas la grosse flèche. Ceci est dû au fait que la grosse flèche lie l'instance de l'objet à, thiscontrairement à la flèche fine, donc les méthodes de flèche fine appelées comme rappels comme ci-dessus ne peuvent pas accéder aux champs de l'instance comme @msgou appeler d'autres méthodes d'instance. La dernière ligne, il existe une solution de contournement pour les cas où la flèche fine a été utilisée.

nicolaskruchten
la source
2
Que faites-vous lorsque vous souhaitez utiliser le thisqui serait appelé à partir de la flèche fine, mais aussi les variables d'instance que vous obtiendriez avec la flèche épaisse?
Andrew Mao
Comme je l'ai dit "La dernière ligne, il existe une solution de contournement pour les cas où la flèche fine a été utilisée."
nicolaskruchten
Je pense que vous avez mal compris ma question. Supposons que la portée par défaut du rappel soit thisdéfinie sur une variable que je souhaite utiliser. Cependant, je souhaite également faire référence à une méthode de classe, je souhaite donc également thisfaire référence à la classe. Je ne peux choisir qu'entre une affectation pour this, alors quelle est la meilleure façon de pouvoir utiliser les deux variables?
Andrew Mao
@AndrewMao, vous devriez probablement publier une question complète sur ce site plutôt que de me demander de répondre dans un commentaire :)
nicolaskruchten
C'est bon, la question n'est pas si importante. Mais je voulais juste préciser que ce n'était pas ce à quoi vous faisiez référence dans votre dernière ligne de code.
Andrew Mao
13

Un point non mentionné dans d'autres réponses et qu'il est important de noter est que la liaison de fonctions avec une grosse flèche lorsque ce n'est pas nécessaire peut conduire à des résultats inattendus, comme dans cet exemple avec une classe que nous appellerons simplement DummyClass.

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

Dans ce cas, les fonctions font exactement ce à quoi on pourrait s'attendre et il ne semble pas y avoir de perte à utiliser la grosse flèche, mais que se passe-t-il lorsque nous modifions le prototype DummyClass après qu'il ait déjà été défini (par exemple, changer une alerte ou changer la sortie d'un journal) :

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

Comme nous pouvons le voir, le remplacement de notre fonction précédemment définie du prototype entraîne le remplacement correct de some_function, mais other_function reste le même sur les instances, car la grosse flèche a entraîné la liaison de other_function de la classe à toutes les instances afin que les instances ne se réfèrent pas à leur classe pour trouver une fonction

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

Même la grosse flèche ne fonctionnera pas car la grosse flèche ne fera que lier la fonction à de nouvelles instances (qui acquièrent les nouvelles fonctions comme on s'y attendrait).

Cependant, cela conduit à des problèmes, que se passe-t-il si nous avons besoin d'une fonction (par exemple dans le cas de la commutation d'une fonction de journalisation vers une boîte de sortie ou quelque chose) qui fonctionnera sur toutes les instances existantes (y compris les gestionnaires d'événements) [en tant que tel, nous ne pouvons pas utiliser flèches grasses dans la définition originale] mais nous avons toujours besoin d'accéder aux attributs internes dans un gestionnaire d'événements [la raison exacte pour laquelle nous avons utilisé des flèches grasses et non des flèches fines].

Eh bien, le moyen le plus simple d'accomplir cela est d'inclure simplement deux fonctions dans la définition de classe d'origine, une définie avec une flèche fine qui effectue les opérations que vous souhaitez exécuter, et une autre définie avec une grosse flèche qui ne fait rien d'autre que d'appeler la première fonction par exemple:

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

Donc, quand utiliser des flèches minces / grasses peut être résumé assez facilement de quatre manières:

  1. Les fonctions de flèche fine seules doivent être utilisées lorsque les deux conditions sont mises:

    • La méthode ne sera jamais passée par référence, y compris event_handlers, par exemple, vous n'avez jamais un cas tel que: some_reference = some_instance.some_method; some_reference ()
    • ET la méthode doit être universelle sur toutes les instances, donc si la fonction prototype change, il en va de même pour la méthode sur toutes les instances
  2. Les fonctions de la grosse flèche seules doivent être utilisées lorsque la condition suivante est remplie:

    • La méthode doit être liée précisément à l'instance lors de la création de l'instance et rester liée en permanence même si la définition de la fonction change pour le prototype, cela inclut tous les cas où la fonction doit être un gestionnaire d'événements et le comportement du gestionnaire d'événements doit être cohérent
  3. La fonction de grosse flèche qui appelle directement une fonction de flèche fine doit être utilisée lorsque les conditions suivantes sont remplies:

    • La méthode doit être appelée par référence comme un gestionnaire d'événements
    • ET la fonctionnalité peut changer à l'avenir affectant les instances existantes en remplaçant la fonction de flèche fine
  4. La fonction de flèche fine qui appelle directement une fonction de flèche grasse (non démontrée) doit être utilisée lorsque les conditions suivantes sont remplies:

    • La fonction de grosse flèche doit toujours être associée à l'instance
    • MAIS la fonction de flèche fine peut changer (même pour une nouvelle fonction qui n'utilise pas la fonction de flèche grasse d'origine)
    • ET la fonction de flèche fine n'a jamais besoin d'être passée par référence

Dans toutes les approches, il doit être considéré dans le cas où les fonctions prototypes pourraient être modifiées si le comportement d'instances spécifiques se comportera correctement ou non, par exemple bien qu'une fonction soit définie avec une grosse flèche, son comportement peut ne pas être cohérent dans une instance si elle appelle une méthode qui est modifiée dans le prototype

Jamesernator
la source
9

Habituellement, ->c'est bien.

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

alert Foo.static() == Foo # true

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

Notez comment la méthode statique renvoie l'objet de classe pour thiset l'instance renvoie l'objet d'instance pour this.

Ce qui se passe, c'est que la syntaxe d'appel fournit la valeur de this. Dans ce code:

foo.bar()

foosera le contexte de la bar()fonction par défaut. Donc, ça marche comme vous le souhaitez. Vous n'avez besoin de la grosse flèche que lorsque vous appelez ces fonctions d'une autre manière qui n'utilise pas la syntaxe dot pour l'invocation.

# 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()

Dans ces deux cas, l'utilisation d'une grosse flèche pour déclarer cette fonction leur permettrait de fonctionner. Mais à moins que vous ne fassiez quelque chose d'étrange, vous n'en avez généralement pas besoin.

Utilisez donc ->jusqu'à ce que vous en ayez vraiment besoin =>et ne l'utilisez jamais =>par défaut.

Alex Wayne
la source
1
Cela échouera si vous faites:x = obj.instance; alert x() == obj # false!
nicolaskruchten
2
Bien sûr, ce sera le cas, mais cela relèverait de «faire le mal». J'ai maintenant édité ma réponse et expliqué juste quand un =>serait nécessaire sur les méthodes statiques / instance d'une classe.
Alex Wayne
Nitpick: // is not a CoffeeScript commentalors que # is a CoffeeScript comment.
nicolaskruchten
En quoi setTimeout foo.bar, 1000«le faire mal»? Utiliser une grosse flèche est beaucoup plus agréable que d'utiliser setTimeout (-> foo.bar()), 1000IMHO.
nicolaskruchten
1
@nicolaskruchten Il y a un cas pour cette syntaxe dans setTimeout, bien sûr. Mais votre premier commentaire est quelque peu artificiel et ne révèle pas un cas d'utilisation légitime, mais révèle simplement comment il pourrait se casser. Je dis simplement que vous ne devriez pas utiliser a à =>moins que vous n'en ayez besoin pour une bonne raison, en particulier sur les méthodes d'instance de classe où cela a un coût de performance lié à la création d'une nouvelle fonction qui doit être liée à l'instanciation.
Alex Wayne
5

juste un exemple de la grosse flèche

ne fonctionne pas: (@canvas undefined)

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

fonctionne: (@canvas défini)

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