При создании класса в CoffeeScript следует ли определять все методы экземпляра с помощью =>
оператора ("жирная стрелка"), а все статические методы - с помощью ->
оператора?
При создании класса в CoffeeScript следует ли определять все методы экземпляра с помощью =>
оператора ("жирная стрелка"), а все статические методы - с помощью ->
оператора?
Ответы:
Нет, я бы не использовал это правило.
Основной вариант использования, который я нашел для жирной стрелки при определении методов, - это когда вы хотите использовать метод в качестве обратного вызова, и этот метод ссылается на поля экземпляра:
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
или вызвать другие методы экземпляра. Последняя строка - это обходной путь для случаев, когда использовалась тонкая стрелка.
this
, которая будет вызываться из тонкой стрелки, а также переменные экземпляра, которые вы получите с помощью жирной стрелки?
this
установлена на переменную, которую я хочу использовать. Однако я также хочу сослаться на метод класса, поэтому я хочу this
также сослаться на класс. Я могу выбрать только одно присвоение для this
, так как лучше всего использовать обе переменные?
Точка, не упомянутая в других ответах, которую важно отметить, заключается в том, что связывание функций жирной стрелкой, когда в этом нет необходимости, может привести к непредвиденным результатам, таким как в этом примере с классом, который мы просто назовем 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
Поэтому, когда использовать тонкие / толстые стрелки, можно довольно просто подвести итог четырьмя способами:
Тонкие стрелки в одиночку должны использоваться, когда выполняются оба условия:
Функции только жирной стрелки следует использовать при выполнении следующего условия:
Функция толстой стрелки, которая напрямую вызывает функцию тонкой стрелки, должна использоваться при соблюдении следующих условий:
Функция тонкой стрелки, которая напрямую вызывает функцию жирной стрелки (не показана), должна использоваться при соблюдении следующих условий:
Во всех подходах следует учитывать случай, когда функции прототипа могут быть изменены, будет ли поведение определенных экземпляров вести себя правильно, например, хотя функция определена жирной стрелкой, ее поведение может быть непоследовательным в экземпляре, если она вызывает метод, измененный в прототипе
Обычно ->
это нормально.
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()
В обоих случаях использование жирной стрелки для объявления этой функции позволит им работать. Но если вы не делаете что-то странное, вам обычно это не нужно.
Так что используйте ->
до тех пор, пока вам действительно не понадобится, =>
и никогда не используйте =>
по умолчанию.
x = obj.instance; alert x() == obj # false!
=>
он может понадобиться в статических методах / методах экземпляра класса.
// is not a CoffeeScript comment
тогда как # is a CoffeeScript comment
.
setTimeout foo.bar, 1000
"делать это неправильно"? setTimeout (-> foo.bar()), 1000
ИМХО использовать жирную стрелку намного приятнее, чем использовать .
setTimeout
. Но ваш первый комментарий несколько надуманный и не раскрывает законного варианта использования, а просто показывает, как он может сломаться. Я просто говорю, что вы не должны использовать a, =>
если вам это не нужно по уважительной причине, особенно в методах экземпляра класса, где это приводит к снижению производительности при создании новой функции, связанной с созданием экземпляра.
просто пример того, как выстоять жирную стрелу
не работает: (@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