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?
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?
Câu trả lời:
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 this
trong 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ư @msg
hoặ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.
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?
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 this
tham 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ì?
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:
Các hàm mũi tên mỏng nên được sử dụng khi cả hai điều kiện đều:
Các hàm mũi tên béo nên được sử dụng khi đáp ứng điều kiện sau:
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:
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:
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
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 this
và 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()
foo
sẽ 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.
x = obj.instance; alert x() == obj # false!
=>
phương thức tĩnh / thể hiện của một lớp.
// is not a CoffeeScript comment
trong khi # is a CoffeeScript comment
.
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()), 1000
IMHO.
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.
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