CoffeeScript เมื่อใดควรใช้ลูกศรอ้วน (=>) เหนือลูกศร (->) และในทางกลับกัน


135

เมื่อสร้างคลาสใน CoffeeScript ควรกำหนดวิธีการอินสแตนซ์ทั้งหมดโดยใช้ตัวดำเนินการ=>("fat arrow") และวิธีการแบบคงที่ทั้งหมดที่กำหนดโดยใช้ตัว->ดำเนินการหรือไม่


คุณสามารถโพสต์โค้ดตัวอย่างได้หรือไม่?
sarnold

ดูคำตอบนี้ด้วยstackoverflow.com/a/17431824/517371
Tobia

คำตอบ:


158

ไม่นั่นไม่ใช่กฎที่ฉันจะใช้

กรณีการใช้งานที่สำคัญที่ฉันพบสำหรับ 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"

ดังที่คุณเห็นคุณอาจประสบปัญหาในการส่งการอ้างอิงไปยังวิธีการของอินสแตนซ์เป็นการโทรกลับหากคุณไม่ใช้ลูกศรอ้วน นี่เป็นเพราะลูกศรอ้วนผูกอินสแตนซ์ของออบเจ็กต์เข้ากับthisในขณะที่ลูกศรเส้นเล็กไม่มีดังนั้นเมธอดลูกศรบาง ๆ ที่เรียกว่าการเรียกกลับด้านบนจึงไม่สามารถเข้าถึงฟิลด์ของอินสแตนซ์ได้เช่น@msgหรือเรียกวิธีการอินสแตนซ์อื่น ๆ บรรทัดสุดท้ายมีวิธีแก้ปัญหาสำหรับกรณีที่มีการใช้ลูกศรเส้นเล็ก


2
คุณจะทำอย่างไรเมื่อต้องการใช้สิ่งthisที่เรียกจากลูกศรเส้นเล็ก แต่ยังรวมถึงตัวแปรอินสแตนซ์ที่คุณจะได้รับจากลูกศรอ้วนด้วย
Andrew Mao

อย่างที่บอก "บรรทัดสุดท้ายมีวิธีแก้ปัญหาสำหรับกรณีที่ใช้ลูกศรเส้นเล็ก"
nicolaskruchten

ฉันคิดว่าคุณเข้าใจคำถามของฉันผิด สมมติว่าขอบเขตเริ่มต้นของการเรียกกลับได้thisตั้งค่าเป็นตัวแปรที่ฉันต้องการใช้ อย่างไรก็ตามฉันต้องการอ้างอิงเมธอดคลาสด้วยดังนั้นฉันจึงต้องการthisอ้างถึงคลาสด้วย ฉันสามารถเลือกระหว่างการมอบหมายงานเดียวเท่านั้นthisดังนั้นวิธีใดดีที่สุดที่จะสามารถใช้ตัวแปรทั้งสองได้
Andrew Mao

@AndrewMao คุณน่าจะโพสต์คำถามเต็ม ๆ ในเว็บไซต์นี้แทนที่จะให้ฉันตอบในความคิดเห็น :)
nicolaskruchten

ไม่เป็นไรคำถามไม่สำคัญ แต่ฉันแค่อยากจะชี้แจงว่ามันไม่ใช่สิ่งที่คุณอ้างถึงในบรรทัดสุดท้ายของโค้ด
Andrew Mao

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

ในกรณีนี้ฟังก์ชั่นจะทำในสิ่งที่คาดหวังและดูเหมือนว่าจะไม่มีการสูญเสียไปกับการใช้ 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 ทำให้ 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. ควรใช้ฟังก์ชัน Thin arrow alone เมื่อตรงตามเงื่อนไขทั้งสอง:

    • วิธีนี้จะไม่ถูกส่งต่อโดยการอ้างอิงรวมถึง event_handlers เช่นคุณไม่เคยมีกรณีเช่น some_reference = some_instance.some_method; some_reference ()
    • และวิธีการนี้ควรเป็นสากลสำหรับทุกอินสแตนซ์ดังนั้นหากฟังก์ชันต้นแบบเปลี่ยนไปวิธีนี้จะใช้กับอินสแตนซ์ทั้งหมด
  2. ควรใช้ฟังก์ชัน Fat arrow alone เมื่อตรงตามเงื่อนไขต่อไปนี้:

    • วิธีนี้ควรถูกผูกไว้อย่างแม่นยำกับอินสแตนซ์ที่สร้างอินสแตนซ์และยังคงถูกผูกไว้อย่างถาวรแม้ว่านิยามฟังก์ชันจะเปลี่ยนไปสำหรับต้นแบบก็ตามซึ่งรวมถึงทุกกรณีที่ฟังก์ชันควรเป็นตัวจัดการเหตุการณ์และพฤติกรรมของตัวจัดการเหตุการณ์ควรสอดคล้อง
  3. ควรใช้ฟังก์ชันลูกศรไขมันซึ่งเรียกใช้ฟังก์ชันลูกศรผอมโดยตรงเมื่อตรงตามเงื่อนไขต่อไปนี้:

    • จำเป็นต้องเรียกเมธอดโดยการอ้างอิงเช่นตัวจัดการเหตุการณ์
    • และฟังก์ชันการทำงานอาจเปลี่ยนแปลงในอนาคตซึ่งส่งผลกระทบต่ออินสแตนซ์ที่มีอยู่โดยการแทนที่ฟังก์ชันลูกศรบาง
  4. ควรใช้ฟังก์ชันลูกศรแบบบางซึ่งเรียกฟังก์ชันลูกศรไขมันโดยตรง (ไม่แสดงให้เห็น) เมื่อตรงตามเงื่อนไขต่อไปนี้:

    • ต้องแนบฟังก์ชันลูกศรไขมันกับอินสแตนซ์เสมอ
    • แต่ฟังก์ชันลูกศรเส้นเล็กอาจเปลี่ยนไป (แม้จะเป็นฟังก์ชันใหม่ที่ไม่ใช้ฟังก์ชันลูกศรอ้วนเดิม)
    • และไม่จำเป็นต้องส่งผ่านฟังก์ชันลูกศรบางโดยการอ้างอิง

ในทุกแนวทางจะต้องพิจารณาในกรณีที่ฟังก์ชันต้นแบบอาจมีการเปลี่ยนแปลงว่าพฤติกรรมสำหรับอินสแตนซ์เฉพาะจะทำงานได้อย่างถูกต้องหรือไม่เช่นแม้ว่าฟังก์ชันจะถูกกำหนดด้วยลูกศรไขมันพฤติกรรมของมันอาจไม่สอดคล้องกันภายในอินสแตนซ์หากเรียกใช้ วิธีการที่เปลี่ยนแปลงภายในต้นแบบ


9

โดยปกติ->เป็นเรื่องปกติ

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

alert Foo.static() == Foo # true

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

หมายเหตุว่าวิธีการแบบคงที่กลับวัตถุชั้นสำหรับและอินสแตนซ์ส่งกลับวัตถุตัวอย่างสำหรับthisthis

thisมีอะไรเกิดขึ้นคือไวยากรณ์การภาวนาคือการให้ค่าของ ในรหัสนี้:

foo.bar()

fooจะเป็นบริบทของbar()ฟังก์ชันโดยค่าเริ่มต้น ดังนั้นเพียงแค่ sorta ทำงานตามที่คุณต้องการ คุณต้องใช้ลูกศรอ้วนเมื่อคุณเรียกใช้ฟังก์ชันเหล่านี้ด้วยวิธีอื่นที่ไม่ใช้ไวยากรณ์จุดสำหรับการเรียกใช้

# 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"ทำมันผิด"? การใช้ fat-arrow นั้นดีกว่าการใช้setTimeout (-> foo.bar()), 1000IMHO มาก
nicolaskruchten

1
@nicolaskruchten มีกรณีของไวยากรณ์setTimeoutนั้นแน่นอน แต่ความคิดเห็นแรกของคุณค่อนข้างมีการจัดทำขึ้นและไม่ได้เปิดเผยกรณีการใช้งานที่ถูกต้อง แต่เพียงแค่เปิดเผยว่ามันอาจแตกได้อย่างไร ฉันแค่บอกว่าคุณไม่ควรใช้ a =>เว้นแต่คุณจะต้องการมันด้วยเหตุผลที่ดีโดยเฉพาะอย่างยิ่งในวิธีการอินสแตนซ์คลาสที่มีต้นทุนด้านประสิทธิภาพในการสร้างฟังก์ชันใหม่ที่จำเป็นต้องเชื่อมโยงกับการสร้างอินสแตนซ์
Alex Wayne

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.