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

133

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

Али Салехи
источник
Вы можете опубликовать образец кода?
sarnold
См. Также этот ответ stackoverflow.com/a/17431824/517371
Tobia

Ответы:

157

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

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

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

nicolaskruchten
источник
2
Что вы делаете, когда хотите использовать функцию this, которая будет вызываться из тонкой стрелки, а также переменные экземпляра, которые вы получите с помощью жирной стрелки?
Эндрю Мао
Как я уже сказал, «Последняя строка - это обходной путь для случаев, когда использовалась тонкая стрелка».
nicolaskruchten
Я думаю, вы неправильно поняли мой вопрос. Предположим, что область действия обратного вызова по умолчанию thisустановлена ​​на переменную, которую я хочу использовать. Однако я также хочу сослаться на метод класса, поэтому я хочу thisтакже сослаться на класс. Я могу выбрать только одно присвоение для this, так как лучше всего использовать обе переменные?
Андрей Мао
@AndrewMao, вероятно, вам следует публиковать полный вопрос на этом сайте, а не
давать
Все в порядке, вопрос не так важен. Но я просто хотел уточнить, что это не то, о чем вы говорили в своей последней строке кода.
Эндрю Мао
13

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

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

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

Jamesernator
источник
9

Обычно ->это нормально.

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

alert Foo.static() == Foo # true

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

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

Происходит то, что синтаксис вызова обеспечивает значение this. В этом коде:

foo.bar()

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

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

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

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

Алекс Уэйн
источник
1
Это не удастся, если вы сделаете:x = obj.instance; alert x() == obj # false!
nicolaskruchten
2
Конечно, будет, но это подпадает под определение «делать неправильно». Теперь я отредактировал свой ответ и объяснил, когда =>он может понадобиться в статических методах / методах экземпляра класса.
Alex Wayne
Nitpick: // is not a CoffeeScript commentтогда как # is a CoffeeScript comment.
nicolaskruchten
Как setTimeout foo.bar, 1000"делать это неправильно"? setTimeout (-> foo.bar()), 1000ИМХО использовать жирную стрелку намного приятнее, чем использовать .
nicolaskruchten
1
@nicolaskruchten Разумеется, такой синтаксис имеет смысл setTimeout. Но ваш первый комментарий несколько надуманный и не раскрывает законного варианта использования, а просто показывает, как он может сломаться. Я просто говорю, что вы не должны использовать a, =>если вам это не нужно по уважительной причине, особенно в методах экземпляра класса, где это приводит к снижению производительности при создании новой функции, связанной с созданием экземпляра.
Алекс Уэйн
5

просто пример того, как выстоять жирную стрелу

не работает: (@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
user3896501
источник