CoffeeScript, когда использовать жирную стрелку (=>) над стрелкой (->) и наоборот - PullRequest
129 голосов
/ 23 января 2012

При создании класса в CoffeeScript следует ли определять все методы экземпляра с помощью оператора => ("жирная стрелка"), а все статические методы - с помощью оператора ->?

Ответы [ 4 ]

152 голосов
/ 23 января 2012

Нет, я бы не использовал это правило.

Основной вариант использования, который я нашел для жирной стрелки при определении методов, - это когда вы хотите использовать метод в качестве обратного вызова, и этот метод ссылается на поля экземпляра:

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"

Как видите, у вас могут возникнуть проблемы с передачей ссылки на метод экземпляра в качестве обратного вызова, если вы не используете жирную стрелку. Это связано с тем, что жирная стрелка привязывает экземпляр объекта к this, а тонкая стрелка - нет, поэтому методы тонкой стрелки, называемые обратными вызовами, как указано выше, не могут получить доступ к полям экземпляра, например @msg, или вызвать другие методы экземпляра. В последней строке есть обходной путь для случаев, когда использовалась тонкая стрелка.

13 голосов
/ 01 декабря 2014

В других ответах важно отметить, что связывание функций жирной стрелкой, когда в этом нет необходимости, может привести к непредвиденным результатам, как, например, в этом примере с классом, который мы просто назовем 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

В этом случае функции делают именно то, что можно ожидать, и использование жирной стрелки, похоже, не потеряно, но что происходит, когда мы модифицируем прототип DummyClass после того, как он уже определен (например, изменение какого-либо предупреждения или изменение вывода журнал):

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

Как мы видим, переопределение нашей ранее определенной функции прототипа приводит к тому, что some_function корректно перезаписывается, но other_function остается неизменной в экземплярах, так как жирная стрелка привела к тому, что other_function из класса привязан ко всем экземплярам, ​​поэтому экземпляры не будут ссылаться обратно в их класс, чтобы найти функцию

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

Даже жирная стрелка не будет работать, так как жирная стрелка вызывает привязку функции только к новым экземплярам (которые получают новые функции, как и следовало ожидать).

Однако это приводит к некоторым проблемам: что если нам понадобится функция (например, в случае переключения функции ведения журнала на окно вывода или что-то подобное), которая будет работать во всех существующих экземплярах (включая обработчики событий) [как таковые, мы можем не используйте жирные стрелки в исходном определении], но нам все еще нужен доступ к внутренним атрибутам в обработчике событий [точная причина, по которой мы использовали жирные стрелки, а не тонкие стрелки].

Что ж, самый простой способ сделать это - просто включить две функции в исходное определение класса: одну, определенную тонкой стрелкой, которая выполняет операции, которые вы хотите выполнить, и другую, определенную жирной стрелкой, которая ничего не делает, кроме вызова первая функция например:

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

Так что, когда использовать тонкие / толстые стрелки, можно довольно просто подвести итог четырьмя способами:

  1. При выполнении обоих условий должны использоваться только функции тонкой стрелки:

    • Метод никогда не будет передан по ссылке, включая event_handlers, например у вас никогда не бывает таких случаев, как: some_reference = some_instance.some_method; some_reference ()
    • И метод должен быть универсальным для всех экземпляров, поэтому, если функция прототипа изменяется, метод изменяется для всех экземпляров
  2. Должны использоваться только функции жирной стрелки, если выполняется следующее условие:

    • Метод должен быть точно привязан к экземпляру при создании экземпляра и оставаться постоянно связанным, даже если определение функции изменяется для прототипа, включая все случаи, когда функция должна быть обработчиком события, а поведение обработчика события должно быть согласованным
  3. Функция жирной стрелки, которая напрямую вызывает функцию тонкой стрелки, должна использоваться при соблюдении следующих условий:

    • Метод должен вызываться по ссылке, такой как обработчик события
    • И функциональность может измениться в будущем, затрагивая существующие экземпляры, заменив функцию тонкой стрелки
  4. Функция тонкой стрелки, которая напрямую вызывает функцию жирной стрелки (не показана), должна использоваться при соблюдении следующих условий:

    • Функция жирной стрелки всегда должна быть присоединена к экземпляру
    • НО функция тонкой стрелки может измениться (даже на новую функцию, которая не использует оригинальную функцию жирной стрелки)
    • И функцию тонкой стрелки не нужно передавать по ссылке

Во всех подходах следует учитывать случай, когда функции прототипа могут быть изменены, будет ли поведение определенных экземпляров вести себя правильно, например, хотя функция определена жирной стрелкой, ее поведение может быть непоследовательным внутри экземпляра. если он вызывает метод, который изменяется в прототипе

9 голосов
/ 23 января 2012

Обычно -> в порядке.

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

alert Foo.static() == Foo # true

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

Обратите внимание, как статический метод возвращает объект класса для this, а экземпляр возвращает объект экземпляра для this.

То, что происходит, заключается в том, что синтаксис вызова предоставляет значение this. В этом коде:

foo.bar()

foo будет контекстом функции bar() по умолчанию. Так что это просто работает так, как вы хотите. Толстая стрелка нужна вам только тогда, когда вы вызываете эту функцию другим способом, который не использует точечный синтаксис для вызова.

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

В обоих этих случаях использование жирной стрелки для объявления этой функции позволит им работать. Но если вы не делаете что-то странное, вам обычно это не нужно.

Так что используйте ->, пока вам действительно не понадобится => и никогда не используйте => по умолчанию.

5 голосов
/ 18 июля 2015

просто пример для выделения жирной стрелки

не работает: (@canvas undefined)

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

работает: (@canvas определено)

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