CoffeeScript, Cuándo usar la flecha gruesa (=>) sobre la flecha (->) y viceversa

133

Al construir una clase en CoffeeScript, ¿se debe definir todo el método de instancia con el =>operador ("flecha gruesa") y todos los métodos estáticos que se definan con el ->operador?

Ali Salehi
fuente
¿Puedes publicar algún código de muestra?
sarnold
Consulta también esta respuesta stackoverflow.com/a/17431824/517371
Tobia

Respuestas:

157

No, esa no es la regla que usaría.

El principal caso de uso que he encontrado para la flecha gruesa en la definición de métodos es cuando desea utilizar un método como devolución de llamada y ese método hace referencia a campos de instancia:

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"

Como puede ver, puede tener problemas para pasar una referencia al método de una instancia como devolución de llamada si no utiliza la flecha gruesa. Esto se debe a que la flecha gruesa une la instancia del objeto thismientras que la flecha delgada no lo hace, por lo que los métodos de flecha delgada llamados como devoluciones de llamada como los anteriores no pueden acceder a los campos de la instancia @msgo llamar a otros métodos de instancia. La última línea es una solución alternativa para los casos en que se ha utilizado la flecha delgada.

nicolaskruchten
fuente
2
¿Qué haces cuando quieres usar el thisque se llamaría desde la flecha delgada, pero también las variables de instancia que obtendrías con la flecha gorda?
Andrew Mao
Como dije "La última línea hay una solución para los casos en los que se ha usado la flecha delgada".
nicolaskruchten
Creo que entendiste mal mi pregunta. Suponga que el alcance predeterminado de la devolución de llamada se ha thisestablecido en una variable que quiero usar. Sin embargo, también quiero hacer referencia a un método de clase, por lo que también quiero thishacer referencia a la clase. Solo puedo elegir entre una tarea this, entonces, ¿cuál es la mejor manera de poder usar ambas variables?
Andrew Mao
@AndrewMao probablemente deberías publicar una pregunta completa en este sitio en lugar de tener que responder en un comentario :)
nicolaskruchten
Está bien, la pregunta no es tan importante. Pero solo quería aclarar que no era a lo que se refería en su última línea de código.
Andrew Mao
13

Un punto no mencionado en otras respuestas que es importante tener en cuenta es que las funciones de enlace con la flecha gruesa cuando no es necesario pueden conducir a resultados no deseados, como en este ejemplo con una clase que llamaremos 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

En este caso, las funciones hacen exactamente lo que uno podría esperar y no parece haber pérdida al usar la flecha gorda, pero qué sucede cuando modificamos el prototipo DummyClass después de que ya se haya definido (por ejemplo, cambiar alguna alerta o cambiar la salida de un registro) :

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

Como podemos ver anulando nuestra función del prototipo definida previamente, hace que alguna función se sobrescriba correctamente, pero otra función sigue siendo la misma en las instancias, ya que la flecha gorda ha provocado que otra función de la clase se vincule a todas las instancias, por lo que las instancias no volverán a su clase para encontrar una función

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

Incluso la flecha gorda no funcionará, ya que la flecha gorda solo hace que la función esté vinculada a nuevas instancias (que obtienen las nuevas funciones como cabría esperar).

Sin embargo, esto lleva a algunos problemas, ¿qué sucede si necesitamos una función (por ejemplo, en el caso de cambiar una función de registro a un cuadro de salida o algo así) que funcione en todas las instancias existentes (incluidos los controladores de eventos) [como tal que no podemos usar flechas gruesas en la definición original] pero aún necesitamos acceso a atributos internos en un controlador de eventos [la razón exacta por la que usamos flechas gruesas no flechas delgadas].

Bueno, la forma más sencilla de lograr esto es simplemente incluir dos funciones en la definición de clase original, una definida con una flecha delgada que realiza las operaciones que desea ejecutar y otra definida con una flecha gruesa que no hace más que llamar a la primera función por ejemplo:

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

Entonces, cuándo usar flechas delgadas / gruesas se puede resumir de manera bastante fácil de cuatro maneras:

  1. Las funciones de flecha delgada solo deben usarse cuando se cumplen ambas condiciones:

    • El método nunca se pasará por referencia, incluidos los controladores de eventos, por ejemplo, nunca tiene un caso como: some_reference = some_instance.some_method; some_reference ()
    • Y el método debe ser universal en todas las instancias, por lo que si la función prototipo cambia, también lo hace el método en todas las instancias
  2. Las funciones de la flecha de grasa sola deben usarse cuando se cumple la siguiente condición:

    • El método debe vincularse con precisión a la instancia en la creación de la instancia y permanecer permanentemente vinculado incluso si la definición de la función cambia para el prototipo, esto incluye todos los casos en que la función debe ser un controlador de eventos y el comportamiento del controlador de eventos debe ser coherente
  3. La función de flecha gruesa que llama directamente a una función de flecha delgada debe usarse cuando se cumplen las siguientes condiciones:

    • Es necesario llamar al método por referencia, como un controlador de eventos
    • Y la funcionalidad puede cambiar en el futuro y afectar las instancias existentes al reemplazar la función de flecha delgada
  4. La función de flecha delgada que llama directamente a una función de flecha gruesa (no demostrada) debe usarse cuando se cumplen las siguientes condiciones:

    • La función de flecha gruesa debe estar siempre asociada a la instancia
    • PERO la función de flecha delgada puede cambiar (incluso a una nueva función que no utiliza la función de flecha gorda original)
    • Y la función de flecha delgada nunca se necesita para pasar por referencia

En todos los enfoques, debe tenerse en cuenta en el caso en que las funciones del prototipo puedan cambiar si el comportamiento para instancias específicas se comportará correctamente, por ejemplo, aunque una función se define con una flecha gruesa, su comportamiento puede no ser coherente dentro de una instancia si llama un método que se cambia dentro del prototipo

Jamesernator
fuente
9

Por lo general, ->está bien.

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

alert Foo.static() == Foo # true

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

Observe cómo el método estático devuelve el objeto de clase para thisy la instancia devuelve el objeto de instancia para this.

Lo que sucede es que la sintaxis de invocación proporciona el valor de this. En este código:

foo.bar()

fooserá el contexto de la bar()función por defecto. Así que simplemente funciona como quieres. Solo necesita la flecha gruesa cuando llama a estas funciones de alguna otra manera que no use la sintaxis de puntos para la invocación.

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

En ambos casos, usar una flecha gruesa para declarar esa función permitiría que esos funcionen. Pero a menos que esté haciendo algo extraño, generalmente no es necesario.

Así que úsala ->hasta que realmente la necesites =>y nunca la uses =>por defecto.

Alex Wayne
fuente
1
Esto fallará si lo hace:x = obj.instance; alert x() == obj # false!
nicolaskruchten
2
Por supuesto que lo hará, pero eso se caería en "hacerlo mal". Ahora he editado mi respuesta y explico cuándo =>se necesitaría a en los métodos estáticos / de instancia de una clase.
Alex Wayne el
Nitpick: // is not a CoffeeScript commentmientras que # is a CoffeeScript comment.
nicolaskruchten
¿Cómo es setTimeout foo.bar, 1000"hacerlo mal"? Usar una flecha gorda es mucho mejor que usar setTimeout (-> foo.bar()), 1000IMHO.
nicolaskruchten
1
@nicolaskruchten Hay un caso para esa sintaxis setTimeout, por supuesto. Pero su primer comentario es algo artificial y no revela un caso de uso legítimo, sino que simplemente revela cómo podría romperse. Simplemente digo que no debe usar un a =>menos que lo necesite por una buena razón, especialmente en los métodos de instancia de clase donde tiene un costo de rendimiento de crear una nueva función que debe estar vinculada a la creación de instancias.
Alex Wayne el
5

solo un ejemplo para resistir la flecha gorda

no funciona: (@canvas undefined)

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

funciona: (@canvas definidas)

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