CoffeeScript, Khi nào nên sử dụng mũi tên béo (=>) trên mũi tên (->) và ngược lại


133

Khi xây dựng một lớp trong CoffeeScript, có nên xác định tất cả phương thức cá thể bằng cách sử dụng toán tử =>("mũi tên béo") và tất cả các phương thức tĩnh được định nghĩa bằng ->toán tử không?


Bạn có thể gửi một số mã mẫu?
đăng

Xem thêm câu trả lời này stackoverflow.com/a/17431824/517371
Tobia

Câu trả lời:


157

Không, đó không phải là quy tắc tôi sẽ sử dụng.

Trường hợp sử dụng chính mà tôi đã tìm thấy cho mũi tên béo khi xác định các phương thức là khi bạn muốn sử dụng một phương thức như một cuộc gọi lại và các trường đối tượng tham chiếu phương thức đó:

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"

Như bạn thấy, bạn có thể gặp vấn đề khi chuyển tham chiếu đến phương thức của một cá thể dưới dạng gọi lại nếu bạn không sử dụng mũi tên béo. Điều này là do mũi tên béo liên kết thể hiện của đối tượng thistrong khi mũi tên mỏng không có, vì vậy các phương thức mũi tên mỏng được gọi là cuộc gọi lại như trên không thể truy cập vào các trường của thể hiện như @msghoặc gọi các phương thức cá thể khác. Dòng cuối cùng có một cách giải quyết cho các trường hợp sử dụng mũi tên mỏng.


2
Bạn sẽ làm gì khi bạn muốn sử dụng cái thisđược gọi từ mũi tên mỏng, nhưng cả các biến thể hiện mà bạn sẽ nhận được bằng mũi tên béo?
Andrew Mao

Như tôi đã nói "Dòng cuối cùng có một cách giải quyết cho các trường hợp mũi tên mỏng đã được sử dụng."
nicolaskruchten

Tôi nghĩ rằng bạn đã hiểu nhầm câu hỏi của tôi. Giả sử rằng phạm vi mặc định của cuộc gọi lại đã thisđược đặt thành một biến mà tôi muốn sử dụng. Tuy nhiên, tôi cũng muốn tham khảo một phương thức lớp, vì vậy tôi cũng muốn thistham khảo lớp. Tôi chỉ có thể chọn giữa một nhiệm vụ cho this, vậy cách tốt nhất để có thể sử dụng cả hai biến là gì?
Andrew Mao

@AndrewMao có lẽ bạn nên đăng một câu hỏi đầy đủ trên trang web này thay vì để tôi trả lời trong một bình luận :)
nicolaskruchten

Không sao, câu hỏi không quan trọng. Nhưng tôi chỉ muốn làm rõ rằng đó không phải là những gì bạn đang đề cập đến trong dòng mã cuối cùng của bạn.
Andrew Mao

13

Một điểm không được đề cập trong các câu trả lời khác rất quan trọng cần lưu ý là các hàm ràng buộc với mũi tên béo khi không cần thiết có thể dẫn đến kết quả ngoài ý muốn, chẳng hạn như trong ví dụ này với một lớp chúng ta sẽ chỉ gọi 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

Trong trường hợp này, các hàm thực hiện chính xác những gì người ta có thể mong đợi và dường như không mất gì khi sử dụng mũi tên béo, nhưng điều gì xảy ra khi chúng ta sửa đổi nguyên mẫu DummyClass sau khi đã được xác định (ví dụ: thay đổi một số cảnh báo hoặc thay đổi đầu ra của nhật ký) :

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

Như chúng ta có thể thấy việc ghi đè chức năng nguyên mẫu được xác định trước đó của chúng ta khiến cho một số hàm bị ghi đè chính xác nhưng các hàm khác vẫn giữ nguyên trong các trường hợp vì mũi tên béo đã khiến cho các hàm khác bị ràng buộc với tất cả các trường hợp, vì vậy các trường hợp sẽ không quay trở lại lớp của chúng để tìm một chức năng

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

Ngay cả mũi tên béo cũng không hoạt động vì mũi tên béo chỉ khiến chức năng bị ràng buộc với các trường hợp mới (có chức năng mới như người ta mong đợi).

Tuy nhiên, điều này dẫn đến một số vấn đề, nếu chúng ta cần một chức năng (ví dụ như trong trường hợp chuyển đổi chức năng ghi nhật ký sang hộp đầu ra hoặc một cái gì đó) sẽ hoạt động trên tất cả các trường hợp hiện tại (bao gồm cả trình xử lý sự kiện) [như vậy chúng ta không thể sử dụng mũi tên béo trong định nghĩa ban đầu] nhưng chúng ta vẫn cần truy cập vào các thuộc tính bên trong trong một trình xử lý sự kiện [lý do chính xác chúng ta sử dụng mũi tên béo không phải mũi tên mỏng].

Cách đơn giản nhất để thực hiện điều này là chỉ bao gồm hai hàm trong định nghĩa lớp gốc, một hàm được định nghĩa bằng một mũi tên mỏng thực hiện các thao tác mà bạn muốn thực hiện và một hàm khác được xác định bằng một mũi tên béo không gọi gì là hàm đầu tiên ví dụ:

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

Vì vậy, khi sử dụng mũi tên mỏng / béo có thể được tóm tắt khá dễ dàng theo bốn cách:

  1. Các hàm mũi tên mỏng nên được sử dụng khi cả hai điều kiện đều:

    • Phương thức sẽ không bao giờ được chuyển qua tham chiếu bao gồm event_handlers, ví dụ: bạn không bao giờ gặp trường hợp nào như: some_Vference = some_instance.some_method; một số thông số ()
    • VÀ phương thức nên phổ quát trên tất cả các trường hợp vì vậy nếu hàm nguyên mẫu thay đổi thì phương thức trên tất cả các trường hợp
  2. Các hàm mũi tên béo nên được sử dụng khi đáp ứng điều kiện sau:

    • Phương thức phải được ràng buộc chính xác với cá thể khi tạo cá thể và duy trì ràng buộc vĩnh viễn ngay cả khi định nghĩa hàm thay đổi cho nguyên mẫu, điều này bao gồm tất cả các trường hợp hàm phải là một trình xử lý sự kiện và hành vi xử lý sự kiện phải nhất quán
  3. Nên sử dụng chức năng mũi tên béo trực tiếp gọi chức năng mũi tên mỏng khi đáp ứng các điều kiện sau:

    • Phương thức này được yêu cầu gọi bằng tham chiếu, chẳng hạn như xử lý sự kiện
    • VÀ chức năng có thể thay đổi trong tương lai ảnh hưởng đến các phiên bản hiện tại bằng cách thay thế chức năng mũi tên mỏng
  4. Nên sử dụng chức năng mũi tên mỏng gọi trực tiếp chức năng mũi tên béo (không được chứng minh) khi đáp ứng các điều kiện sau:

    • Hàm mũi tên béo phải luôn được gắn vào ví dụ
    • NHƯNG chức năng mũi tên mỏng có thể thay đổi (ngay cả chức năng mới không sử dụng chức năng mũi tên chất béo ban đầu)
    • VÀ chức năng mũi tên mỏng không bao giờ cần phải được thông qua tham chiếu

Trong tất cả các cách tiếp cận, nó phải được xem xét trong trường hợp các hàm nguyên mẫu có thể được thay đổi cho dù hành vi của các trường hợp cụ thể có hành xử đúng hay không, mặc dù một hàm được định nghĩa bằng một mũi tên béo, hành vi của nó có thể không nhất quán trong một thể hiện nếu nó gọi một phương thức được thay đổi trong nguyên mẫu


9

Thông thường, ->là tốt.

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

alert Foo.static() == Foo # true

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

Lưu ý cách phương thức tĩnh trả về đối tượng lớp cho thisvà thể hiện trả về đối tượng thể hiện cho this.

Điều gì đang xảy ra là cú pháp gọi đang cung cấp giá trị của this. Trong mã này:

foo.bar()

foosẽ là bối cảnh của bar()chức năng theo mặc định. Vì vậy, nó chỉ sắp xếp làm việc như bạn muốn. Bạn chỉ cần mũi tên béo khi bạn gọi các hàm này theo một số cách khác không sử dụng cú pháp dấu chấm để gọi.

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

Trong cả hai trường hợp đó, sử dụng mũi tên béo để khai báo hàm đó sẽ cho phép các hàm đó hoạt động. Nhưng trừ khi bạn đang làm một cái gì đó kỳ lạ, bạn thường không cần phải làm.

Vì vậy, sử dụng ->cho đến khi bạn thực sự cần =>và không bao giờ sử dụng =>theo mặc định.


1
Điều này sẽ thất bại nếu bạn làm:x = obj.instance; alert x() == obj # false!
nicolaskruchten

2
Tất nhiên là có, nhưng điều đó sẽ thuộc "làm sai". Bây giờ tôi đã chỉnh sửa câu trả lời của mình và giải thích ngay khi cần một =>phương thức tĩnh / thể hiện của một lớp.
Alex Wayne

Nitpick: // is not a CoffeeScript commenttrong khi # is a CoffeeScript comment.
nicolaskruchten

Làm thế nào là setTimeout foo.bar, 1000"làm sai"? Sử dụng mũi tên béo sẽ đẹp hơn nhiều so với sử dụng setTimeout (-> foo.bar()), 1000IMHO.
nicolaskruchten

1
@nicolaskruchten Tất nhiên có một trường hợp cho cú pháp đó setTimeout. Nhưng bình luận đầu tiên của bạn có phần bị tước đoạt và không tiết lộ trường hợp sử dụng hợp pháp, mà chỉ tiết lộ cách nó có thể bị phá vỡ. Tôi chỉ đơn giản nói rằng bạn không nên sử dụng =>trừ khi bạn cần nó vì một lý do chính đáng, đặc biệt là đối với các phương thức cá thể lớp trong đó nó có chi phí hiệu năng để tạo một hàm mới cần phải được ràng buộc trong việc khởi tạo.
Alex Wayne

5

chỉ là một ví dụ cho việc không hiểu mũi tên béo

không hoạt động: (@canvas không xác định)

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

hoạt động: (@canvas xác định)

class Test
  constructor: ->
    @canvas = document.createElement 'canvas'
    window.addEventListener 'resize', =>
      @canvas.width = window.innerWidth
      @canvas.height = window.innerHeight
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.