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


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

4 126

4 ответа:

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

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

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"

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

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

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

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

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

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

как правило, -> - это хорошо.

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

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

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

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

как раз пример для unstanding тучная стрелка

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

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

works: (@canvas defined)

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