CoffeeScript, Kapan harus menggunakan panah gemuk (=>) di atas panah (->) dan sebaliknya

133

Ketika membangun kelas di CoffeeScript, haruskah semua metode instance didefinisikan menggunakan =>operator ("panah gemuk") dan semua metode statis didefinisikan menggunakan ->operator?

Ali Salehi
sumber
Bisakah Anda memposting beberapa kode sampel?
sarnold
Lihat juga jawaban ini stackoverflow.com/a/17431824/517371
Tobia

Jawaban:

157

Tidak, itu bukan aturan yang akan saya gunakan.

Kasus penggunaan utama yang saya temukan untuk panah-gemuk dalam mendefinisikan metode adalah ketika Anda ingin menggunakan metode sebagai panggilan balik dan metode itu merujuk bidang contoh:

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"

Seperti yang Anda lihat, Anda mungkin mengalami masalah dalam meneruskan referensi ke metode instance sebagai panggilan balik jika Anda tidak menggunakan panah gemuk. Ini karena panah-lemak mengikat instance objek ke thissedangkan panah-tipis tidak, sehingga metode panah-tipis yang disebut sebagai callback seperti di atas tidak dapat mengakses bidang instance seperti @msgatau memanggil metode instance lain. Baris terakhir ada solusi untuk kasus di mana panah tipis telah digunakan.

nicolaskruchten
sumber
2
Apa yang Anda lakukan ketika Anda ingin menggunakan thisyang akan dipanggil dari panah tipis, tetapi juga variabel instan yang akan Anda dapatkan dengan panah gemuk?
Andrew Mao
Seperti yang saya katakan, "Baris terakhir ada solusi untuk kasus di mana panah tipis telah digunakan."
nicolaskruchten
Saya pikir Anda salah mengerti pertanyaan saya. Misalkan ruang lingkup default dari callback telah thisdiatur ke variabel yang ingin saya gunakan. Namun, saya juga ingin merujuk metode kelas, jadi saya ingin thismerujuk ke kelas juga. Saya hanya dapat memilih antara satu tugas this, jadi apa cara terbaik untuk dapat menggunakan kedua variabel?
Andrew Mao
@AndrewMao Anda mungkin harus mengirim pertanyaan lengkap di situs ini daripada meminta saya menjawab dalam komentar :)
nicolaskruchten
Tidak apa-apa, pertanyaan tidak terlalu penting. Tapi saya hanya ingin mengklarifikasi bahwa itu bukan apa yang Anda maksudkan di baris kode terakhir Anda.
Andrew Mao
13

Suatu hal yang tidak disebutkan dalam jawaban lain yang penting untuk dicatat adalah bahwa fungsi pengikatan dengan panah gemuk ketika tidak perlu dapat menyebabkan hasil yang tidak diinginkan seperti dalam contoh ini dengan kelas kita hanya akan memanggil 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

Dalam hal ini fungsi melakukan persis apa yang diharapkan dan tampaknya tidak ada kerugian menggunakan panah gemuk, tetapi apa yang terjadi ketika kita memodifikasi prototipe DummyClass setelah itu sudah ditentukan (misalnya mengubah beberapa peringatan atau mengubah output dari log) :

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

Seperti yang dapat kita lihat, mengesampingkan fungsi prototipe kita yang telah didefinisikan sebelumnya menyebabkan some_function ditimpa dengan benar tetapi fungsi other_ tetap sama pada instance karena panah lemak telah menyebabkan fungsi_fungsi dari kelas terikat ke semua instance sehingga instance tidak akan merujuk kembali ke kelas mereka untuk menemukan fungsi

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

Bahkan panah gemuk tidak akan berfungsi sebagai panah gemuk hanya menyebabkan fungsi terikat ke instance baru (yang memang memperoleh fungsi baru seperti yang diharapkan).

Namun ini mengarah ke beberapa masalah, bagaimana jika kita membutuhkan fungsi (misalnya dalam kasus beralih fungsi logging ke kotak keluaran atau sesuatu) yang akan bekerja pada semua instance yang ada (termasuk event handler) [karena itu kita tidak dapat menggunakan panah gemuk dalam definisi asli] tetapi kita masih membutuhkan akses ke atribut internal dalam event handler [alasan tepatnya kami menggunakan panah gemuk bukan panah tipis].

Nah cara paling sederhana untuk mencapai ini adalah dengan hanya memasukkan dua fungsi dalam definisi kelas asli, satu didefinisikan dengan panah tipis yang melakukan operasi yang ingin Anda jalankan, dan yang lain didefinisikan dengan panah gemuk yang tidak melakukan apa-apa selain memanggil fungsi pertama sebagai contoh:

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

Jadi kapan menggunakan panah tipis / gemuk dapat diringkas dengan cukup mudah dalam empat cara:

  1. Fungsi panah tipis saja harus digunakan ketika kedua kondisi terpenuhi:

    • Metode ini tidak akan pernah diteruskan oleh referensi termasuk event_handlers misalnya Anda tidak pernah memiliki kasus seperti: some_reference = some_instance.some_method; some_reference ()
    • DAN metode harus universal atas semua instance jadi jika fungsi prototipe berubah demikian juga metode atas semua instance
  2. Fungsi panah gemuk saja harus digunakan ketika kondisi berikut terpenuhi:

    • Metode harus terikat tepat ke instance pada penciptaan instance dan tetap terikat secara permanen bahkan jika definisi fungsi berubah untuk prototipe, ini mencakup semua kasus di mana fungsi harus menjadi pengendali event dan perilaku event handler harus konsisten
  3. Fungsi panah gemuk yang secara langsung memanggil fungsi panah tipis harus digunakan ketika kondisi berikut terpenuhi:

    • Metode ini harus dipanggil dengan referensi seperti event handler
    • DAN fungsionalitas dapat berubah di masa depan yang memengaruhi instance yang ada dengan mengganti fungsi panah tipis
  4. Fungsi panah tipis yang langsung memanggil fungsi panah gemuk (tidak diperlihatkan) harus digunakan ketika kondisi berikut dipenuhi:

    • Fungsi panah gemuk harus selalu melekat pada instance
    • TAPI fungsi panah tipis dapat berubah (bahkan ke fungsi baru yang tidak menggunakan fungsi panah gemuk asli)
    • DAN fungsi panah tipis tidak perlu dilewatkan dengan referensi

Dalam semua pendekatan itu harus dipertimbangkan dalam kasus di mana fungsi prototipe dapat diubah apakah perilaku untuk kasus tertentu akan berperilaku dengan benar misalnya misalnya meskipun fungsi didefinisikan dengan panah gemuk, perilakunya mungkin tidak konsisten dalam sebuah instance jika ia memanggil sebuah metode yang diubah dalam prototipe

Jamesernator
sumber
9

Biasanya ->baik-baik saja.

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

alert Foo.static() == Foo # true

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

Perhatikan bagaimana metode statis mengembalikan objek kelas untuk thisdan instance mengembalikan objek instance untuk this.

Apa yang terjadi adalah bahwa sintaks doa memberikan nilai this. Dalam kode ini:

foo.bar()

fooakan menjadi konteks bar()fungsi secara default. Jadi itu hanya bekerja sesukamu. Anda hanya perlu panah gemuk ketika Anda memanggil fungsi ini dengan cara lain yang tidak menggunakan sintaks dot untuk doa.

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

Dalam kedua kasus tersebut, menggunakan panah gemuk untuk menyatakan bahwa fungsi akan memungkinkan mereka untuk bekerja. Tetapi kecuali jika Anda melakukan sesuatu yang aneh, Anda biasanya tidak perlu melakukannya.

Jadi gunakan ->sampai Anda benar-benar membutuhkan =>dan tidak pernah menggunakan =>secara default.

Alex Wayne
sumber
1
Ini akan gagal jika Anda melakukannya:x = obj.instance; alert x() == obj # false!
nicolaskruchten
2
Tentu saja akan, tetapi itu akan jatuh di bawah "melakukannya salah". Saya sekarang telah mengedit jawaban saya dan menjelaskan kapan =>akan diperlukan pada metode statis / instance kelas.
Alex Wayne
Nitpick: // is not a CoffeeScript commentsedangkan # is a CoffeeScript comment.
nicolaskruchten
Bagaimana setTimeout foo.bar, 1000"salah melakukannya"? Menggunakan panah gemuk jauh lebih baik daripada menggunakan setTimeout (-> foo.bar()), 1000IMHO.
nicolaskruchten
1
@nicolaskruchten Ada kasus untuk sintaks di setTimeout, tentu saja. Tetapi komentar pertama Anda agak dibuat-buat dan tidak mengungkapkan kasus penggunaan yang sah, tetapi hanya mengungkapkan bagaimana itu bisa pecah. Saya hanya mengatakan bahwa Anda tidak boleh menggunakan a =>kecuali jika Anda membutuhkannya untuk alasan yang baik, terutama pada metode instance kelas di mana ia memiliki biaya kinerja untuk menciptakan fungsi baru yang perlu terikat pada Instansiasi.
Alex Wayne
5

hanya sebuah contoh untuk panah lemak yang luar biasa

tidak berfungsi: (@canvas tidak terdefinisi)

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

berfungsi: (didefinisikan oleh @canvas)

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