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дзвінками, як вище, не можуть отримати доступ до полів екземпляра, як або викликати інші методи екземпляра. В останньому рядку є вирішення випадків, коли була використана тонка стрілка.


2
Що ви робите, коли хочете використовувати те, thisщо називалося б із тонкої стрілки, а також змінні екземпляри, які ви отримаєте зі стрілкою жиру?
Ендрю Мао

Як я вже говорив, "Останній рядок є вирішенням випадків, коли була використана тонка стрілка".
nicolaskruchten

Я думаю, ви неправильно зрозуміли моє запитання. Припустимо, що сфера зворотного дзвінка за замовчуванням thisвстановила змінну, яку я хочу використовувати. Однак я також хочу посилатися на метод класу, тому я хочу thisтакож посилатися на клас. Я можу вибрати лише одне завдання для this, тож який найкращий спосіб мати можливість використовувати обидві змінні?
Ендрю Мао

@AndrewMao ви, ймовірно, повинні залишити повне запитання на цьому веб-сайті, а не відповідати мені в коментарі :)
nicolaskruchten

Це нормально, питання не так важливо. Але я просто хотів уточнити, що це не те, про що ви мали на увазі в своєму останньому рядку коду.
Ендрю Мао

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

Як ми бачимо, що переосмислення нашої раніше визначеної функції прототипу призводить до того, що деяка функція буде правильно перезаписана, але функція 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_instan.some_method; some_reference ()
    • І метод повинен бути універсальним для всіх примірників, тому, якщо змінюється функція прототипу, це також робить метод для всіх екземплярів
  2. Функції, призначені лише для жирової стрілки, повинні використовуватися, коли виконується наступна умова:

    • Метод повинен бути прив’язаний саме до екземпляра у створенні екземпляра і залишатися постійно зв'язаним, навіть якщо визначення функції змінюється для прототипу, це включає всі випадки, коли функція повинна бути обробником подій і поведінка обробника подій повинна бути послідовною
  3. Функція жирної стрілки, яка безпосередньо викликає функцію тонкої стрілки, повинна використовуватися, коли виконуються наступні умови:

    • Метод потрібно викликати посиланням, таким як обробник подій
    • І функціональність може змінитися в майбутньому, впливаючи на існуючі екземпляри, замінивши функцію тонкої стрілки
  4. Функція тонкої стрілки, яка безпосередньо викликає жирову стрілку (не демонструється), повинна використовуватися, коли виконуються наступні умови:

    • Функція стрілки жиру повинна бути завжди прикріплена до екземпляра
    • АЛЕ функція тонкої стрілки може змінитися (навіть до нової функції, яка не використовує оригінальну функцію стрілки жиру)
    • І функцію тонкої стрілки ніколи не потрібно передавати за посиланням

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


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

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

В обох цих випадках використання жирової стрілки для оголошення цієї функції дозволило б їм працювати. Але якщо ви робите щось дивне, зазвичай цього не потрібно.

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


1
Це не вдасться, якщо ви зробите:x = obj.instance; alert x() == obj # false!
nicolaskruchten

2
Звичайно, це буде, але це підпадає під "робити це неправильно". Зараз я відредагував свою відповідь і пояснював, коли потрібен =>буде метод статичних / екземплярів методів класу.
Алекс Уейн

Нітпік: // 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.