Tại sao chúng ta cần các chức năng gọi lại trong Tiếng Việt?


16

Tôi đang đọc cuốn sách programming in Lua. Nó nói rằng

Đóng cửa cung cấp một công cụ có giá trị trong nhiều bối cảnh. Như chúng ta đã thấy, chúng hữu ích như các đối số cho các hàm bậc cao hơn như sắp xếp. Các bao đóng có giá trị đối với các hàm cũng xây dựng các hàm khác, như ví dụ newCorer của chúng tôi; cơ chế này cho phép các chương trình Lua kết hợp các kỹ thuật lập trình tinh vi từ thế giới chức năng. Đóng cửa cũng hữu ích cho các chức năng gọi lại. Một ví dụ điển hình ở đây xảy ra khi bạn tạo các nút trong bộ công cụ GUI thông thường. Mỗi nút có chức năng gọi lại được gọi khi người dùng nhấn nút; bạn muốn các nút khác nhau để làm những việc hơi khác nhau khi nhấn. Chẳng hạn, một máy tính kỹ thuật số cần mười nút tương tự, một nút cho mỗi chữ số. Bạn có thể tạo mỗi cái với một chức năng như thế này:

function digitButton (digit)
  return Button{label = tostring(digit),
                action = function ()
                  add_to_display(digit)
                  end}
end

Có vẻ như nếu tôi gọi digitButton, nó sẽ trả về action(điều này sẽ tạo ra một bao đóng), vì vậy, tôi có thể truy cập vào digitthông qua digitButton.

Câu hỏi của tôi là:

Why we need call back functions? what situations can I apply this to?


Tác giả cho biết:

Trong ví dụ này, chúng tôi giả định rằng Nút là một chức năng của bộ công cụ tạo ra các nút mới; nhãn là nhãn nút; và hành động là đóng cửa gọi lại được gọi khi nhấn nút. Cuộc gọi lại có thể được gọi một thời gian dài sau khi DigitButton thực hiện nhiệm vụ của mình và sau khi chữ số biến cục bộ đi ra khỏi phạm vi, nhưng nó vẫn có thể truy cập vào biến này.

Theo tác giả, tôi nghĩ một ví dụ tương tự như thế này:

function Button(t)
  -- maybe you should set the button here
  return t.action -- so that you can call this later
end

function add_to_display(digit)
  print ("Display the button label: " .. tostring(digit))
end

function digitButton(digit)
  return Button{label = tostring(digit),
                action = function ()
                           add_to_display(digit)
                           end}
end

click_action = digitButton(10)
click_action()

do đó, the callback can be called a long time after digitButton did its task and after the local variable digit went out of scope.


Câu trả lời:


45

Guy 1 to Guy 2: hey anh bạn tôi muốn làm gì đó khi người dùng nhấp vào đó, gọi lại cho tôi khi điều đó xảy ra được không?

Guy 2 gọi lại Guy 1 khi người dùng nhấp vào đây.


1
Tôi rất thích trò đùa gọi lại này :-P
Lucas Li

6
Chỉ tôi thôi à? Tôi không hiểu làm thế nào điều này giải thích tại sao chúng ta cần các hàm gọi lại.
phant0m

2
Nó giống như Guy 1 nói với Guy 2 "khi thời điểm thích hợp làm điều này". Guy 2 đi về ngày của mình và khi thời điểm thích hợp làm điều đó. Nếu bạn có nghĩa là Guy 1 là người gọi của Guy 2, thì điều này không chính xác vì Guy 2 không gọi lại Guy 1, anh ta gọi vào việc cần làm. Thực tế nếu Guy 1 là người gọi của Guy 2, bạn sẽ có một vòng lặp khó khăn trong tay trong kịch bản này. Nếu bạn muốn Guy 1 gọi lại thì điều này không chính xác vì cuộc gọi lại không biết Guy 2 là ai và chắc chắn không yêu cầu anh ta gọi.
nghĩa

Đây là một lời giải thích khủng khiếp.
bên trong

21

Không, nó sẽ không bao giờ trả lại hành động. Nút sẽ thực thi nó khi người dùng nhấp vào nó. Và đó là câu trả lời. Bạn cần các hàm gọi lại khi bạn muốn xác định các hành động sẽ xảy ra trong một thành phần khác để phản ứng với một sự kiện mà bạn không kiểm soát (vòng lặp sự kiện, là một phần của hệ thống, sẽ thực thi một phương thức của nút sẽ lần lượt thực thi hành động).


Tôi không đồng ý với bạn. Tôi đã chỉnh sửa bài viết của mình và thêm một số mã. Tôi vẫn nghĩ rằng hành động không được thực hiện ngay lập tức.
Lucas Li

2
Tim, bạn nói đúng - nhưng Jan cũng vậy. "Nút sẽ thực thi khi người dùng nhấp vào nó", không phải ngay lập tức.
AlexanderBrevig

@AlexanderBrevig yeah, tôi vẫn nghĩ rằng câu trả lời của Jan rất hữu ích cho tôi. Nó cho tôi biết thế nào là gọi lại và tại sao chúng ta cần gọi lại.
Lucas Li

Gọi lại chỉ là một tên bẩn mà ai đó đã thêm vào chức năng thứ hai hoạt động khi chức năng đầu tiên được kích hoạt và có thể dựa trên một tiêu chí. Gọi hàm của bạn well_done () và sẽ không có sự khác biệt. Gọi lại cũng giống như Xác minh, xác minh là một chuỗi các tin nhắn, thường là hai và gọi lại là một chức năng bí mật, thường là hai. Nếu bạn xâu chuỗi hơn 3 cuộc gọi lại, xin chúc mừng, bạn thích làm mọi thứ một cách khó khăn.
m3nda

16

Tôi nghĩ một ví dụ tốt hơn cho việc sử dụng các cuộc gọi lại là trong các chức năng không đồng bộ. Tôi không thể nói cho Lua, nhưng trong JavaScript, một trong những hành động không đồng bộ phổ biến nhất là liên hệ với máy chủ qua AJAX.

Cuộc gọi AJAX không đồng bộ, nghĩa là nếu bạn làm một cái gì đó như thế này:

var ajax = ajaxCall();
if(ajax == true) {
  // Do something
}

nội dung của ifkhối sẽ không chạy một cách chính xác và khi có, chỉ vì ajaxCall()chức năng đã kết thúc trước khi thực hiện đạt được ifcâu lệnh.

Tuy nhiên, các cuộc gọi lại loại bỏ vấn đề này, bằng cách đảm bảo cuộc gọi không đồng bộ kết thúc trước khi gọi chức năng được yêu cầu. Vì vậy, mã của bạn sẽ thay đổi thành một cái gì đó như thế này:

function ajaxCallback(response) {   
  if(response == true) {
    // Do something   
  }
}

ajaxCall(ajaxCallback);

Mục đích của việc này là cho phép bạn làm những việc như thu thập dữ liệu mà không can thiệp vào những thứ khác, chẳng hạn như vẽ giao diện. Nếu việc thu thập dữ liệu là đồng bộ, thì giao diện sẽ không phản hồi khi ứng dụng chờ để lấy dữ liệu. Giao diện không phản hồi rất tệ cho trải nghiệm người dùng, bởi vì hầu hết người dùng sẽ nghĩ rằng ứng dụng đã bị "sập" và sẽ cố gắng chấm dứt quá trình.

Để thấy điều này trong thực tế, bạn chỉ cần xem bất kỳ trang web nào thực hiện bất kỳ loại cập nhật nào mà không cần tải lại toàn bộ trang. Twitter, LinkedIn và các trang web StackExchange là những ví dụ điển hình. Twitter "cuộn vô tận" các nguồn cấp dữ liệu và thông báo của SE (cho cả thông báo người dùng và thông báo rằng một câu hỏi có hoạt động mới), được cung cấp bởi các cuộc gọi không đồng bộ. Khi bạn thấy một công cụ quay vòng ở đâu đó, điều đó có nghĩa là phần đó đã thực hiện cuộc gọi không đồng bộ và đang chờ cuộc gọi đó kết thúc, nhưng bạn có thể tương tác với những thứ khác trên giao diện (và thậm chí thực hiện các cuộc gọi không đồng bộ khác). Khi cuộc gọi kết thúc, nó sẽ phản ứng và cập nhật giao diện tương ứng.


12

Bạn đã hỏi câu hỏi

Tại sao chúng ta cần "gọi lại chức năng"?

Tôi sẽ cố gắng trả lời một cách ngắn gọn và rõ ràng (nếu tôi thất bại ở đó, vui lòng tham khảo liên kết wiki ở cuối câu trả lời này).

Các cuộc gọi lại được sử dụng để trì hoãn việc thực hiện cụ thể của một cái gì đó đến thời điểm cuối cùng có thể.

Ví dụ nút là một minh họa tốt về điều này. Giả sử bạn muốn có một nút trong ứng dụng in một trang tới máy in, chúng tôi có thể tưởng tượng một thế giới nơi bạn sẽ phải mã hóa toàn bộ một PrinterButtonlớp mới để thực hiện việc này.

May mắn thay, chúng ta có khái niệm về các cuộc gọi lại (kế thừa và việc sử dụng mẫu mẫu cũng là một loại ngữ nghĩa gọi lại) vì vậy chúng ta không cần phải làm điều đó.

Thay vì thực hiện lại mọi khía cạnh của một nút, điều chúng tôi làm là thêm vào việc thực hiện nút. Ý Buttontưởng làm một cái gì đó khi nó được nhấn. Chúng tôi chỉ đơn giản nói với nó những gì đó là một cái gì đó.

Cơ chế tiêm hành vi đó vào các khung được gọi là gọi lại.

Thí dụ:

Hãy đưa một số hành vi vào một nút HTML:

<button onclick="print()">Print page through a callback to the print() method</button>

Trong trường hợp này, onclicktrỏ đến một cuộc gọi lại, mà ví dụ này là print()phương thức.


http://en.wikipedia.org/wiki/Callback_(computer_programming)


Cảm ơn, sau khi đọc minh họa của bạn, tôi đi đọc wiki có ví dụ chỉ là các cuộc gọi lại đồng bộ.
Lucas Li

nói với một chức năng câu trả lời mỗi khi nó có vấn đề và bạn phải lắng nghe vấn đề; dạy một chức năng để giải quyết vấn đề cần tự chăm sóc.
Joe

8

Tại sao chúng ta cần gọi lại chức năng? những tình huống nào tôi có thể áp dụng điều này cho?

Các hàm gọi lại rất hữu ích trong lập trình hướng sự kiện. Chúng cho phép bạn thiết lập chương trình của mình theo cách các sự kiện sẽ kích hoạt mã chính xác. Điều này rất phổ biến trong các chương trình có GUI nơi người dùng có thể nhấp vào bất kỳ yếu tố nào trên giao diện người dùng (chẳng hạn như các nút hoặc các mục menu) và mã phù hợp sẽ được chạy.


1
+ Đó là những gì gọi lại là tốt cho, nhưng tôi ghét phải viết chúng :-)
Mike Dunlavey

Khi tôi chỉ là một sinh viên năm nhất, giáo viên của tôi đã dạy chúng tôi cách lập trình trong MFC, nhưng anh ấy không bao giờ nói với chúng tôi thế nào là gọi lại :-(
Lucas Li

2

Điều gì xảy ra mà không có cuộc gọi lại:

Chàng trai 1: Được rồi, tôi đang chờ điều đó xảy ra. (huýt sáo, ngón tay cái twiddles)

Chàng trai 2: Chết tiệt! Tại sao Guy 1 không hiển thị cho tôi công cụ? Tôi muốn xem những thứ xảy ra !! Đồ của tôi đâu Làm thế nào có ai có thể làm bất cứ điều gì được thực hiện xung quanh đây?


1

Trước hết, thuật ngữ gọi lại đề cập đến việc đóng cửa đang được sử dụng cho một cái gì đó.

Ví dụ: giả sử bạn tạo một hàm đóng và chỉ lưu nó trong một biến. Nó không phải là một cuộc gọi lại, bởi vì nó không được sử dụng cho bất cứ điều gì.

Nhưng, giả sử bạn tạo một bao đóng và lưu trữ nó ở đâu đó mà nó sẽ được gọi khi có điều gì đó xảy ra. Bây giờ nó được gọi là một cuộc gọi lại.

Thông thường, các cuộc gọi lại được tạo bởi các phần khác nhau của chương trình so với các phần gọi chúng. Vì vậy, thật dễ dàng để tưởng tượng rằng một số phần của chương trình đang "gọi lại" các phần khác.

Nói một cách đơn giản, các cuộc gọi lại cho phép một phần của chương trình nói với một phần khác để làm một cái gì đó (bất cứ điều gì) khi có điều gì đó xảy ra.

Đối với các biến được giữ sống do được sử dụng trong bao đóng, đó là một tính năng hoàn toàn khác gọi là giá trị tăng (hay còn gọi là "kéo dài thời gian tồn tại của biến cục bộ" trong số những người không nói tiếng Lua). Và nó cũng khá hữu ích.


vâng, Extending the lifetime of local variablescũng là để nói thuật ngữ upvalue.
Lucas Li

Hừm, thú vị. Thuật ngữ upvalue dường như dành riêng cho Lua, mặc dù một tìm kiếm nhanh của Google cho thấy rằng đôi khi nó được sử dụng để mô tả các ngôn ngữ khác. Tôi sẽ thêm nó vào câu trả lời của tôi.
Jonathan Graef

1

Các cuộc gọi lại đã xuất hiện từ những ngày đầu của Fortran, ít nhất. Ví dụ: nếu bạn có bộ giải ODE (phương trình vi phân thường), chẳng hạn như bộ giải Runge-Kutta, nó có thể trông như thế này:

subroutine rungekutta(neq, t, tend, y, deriv)
  integer neq             ! number of differential equations
  double precision t      ! initial time
  double precision tend   ! final time
  double precision y(neq) ! state vector
  ! deriv is the callback function to evaluate the state derivative
  ...
  deriv(neq, t, y, yprime) ! call deriv to get the state derivative
  ...
  deriv(neq, t, y, yprime) ! call it several times
  ...
end subroutine

Nó cho phép người gọi tùy chỉnh hành vi của chương trình con bằng cách chuyển cho nó một hàm mục đích đặc biệt được gọi khi cần thiết. Điều này cho phép bộ giải ODE được sử dụng để mô phỏng nhiều hệ thống khác nhau.


1

Tình cờ gặp phải vấn đề này và muốn đưa ra câu trả lời cập nhật hơn ...

Các hàm gọi lại cho phép chúng ta làm một cái gì đó với dữ liệu sau đó , cho phép phần còn lại của mã của chúng ta chạy, thay vì chờ đợi nó. Mã không đồng bộ cho phép chúng tôi thực hiện bất kỳ điều gì sau này . Ví dụ dễ đọc nhất về các hàm gọi lại mà tôi đã gặp là Promise trong JavaScript. Trong ví dụ dưới đây mỗi khi bạn thấy hàm (kết quả) hoặc (newResult) hoặc (FinalResult) ... đây là các hàm gọi lại. Mã trong dấu ngoặc nhọn của chúng được thực thi khi dữ liệu quay trở lại từ máy chủ. Chỉ tại thời điểm này, nó sẽ có ý nghĩa để thực hiện các chức năng này kể từ bây giờ dữ liệu họ cần có sẵn.

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

mã được lấy từ ... https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

Hy vọng, điều này sẽ giúp được ai đó. Đây là những gì đã giúp tôi hiểu cuộc gọi lại :)


1
điều này dường như không cung cấp bất cứ điều gì đáng kể qua các điểm được thực hiện và giải thích trong 9 câu trả lời trước
gnat

1
Có thể cho bạn nhưng đây là những gì thực sự làm cho cuộc gọi lại rõ ràng cho tôi vì vậy tôi nghĩ rằng tôi sẽ chia sẻ. IMO khả năng đọc của ví dụ là những gì làm cho nó đáng giá. Tôi chắc chắn rằng chúng ta có thể đồng ý khả năng đọc mã đi một chặng đường dài, đặc biệt là cho người mới bắt đầu.
NRV

-2

Tôi không biết lua nhưng trong các phương thức gọi lại nói chung, một số thứ như đa luồng. Ví dụ, trong lập trình phát triển ứng dụng di động, hầu hết các ứng dụng đều hoạt động như gửi yêu cầu đến máy chủ và chơi với UI với dữ liệu được gửi dưới dạng phản hồi từ máy chủ. Khi người dùng gửi yêu cầu đến máy chủ, sẽ mất thời gian để nhận phản hồi từ máy chủ nhưng đối với UI UX tốt nhất không nên bị kẹt.

Do đó, chúng tôi sử dụng nhiều luồng để thực hiện các hoạt động song song. Khi chúng tôi nhận được phản hồi từ máy chủ, chúng tôi cần cập nhật UI. chúng tôi phải thông báo từ chủ đề đó để cập nhật. từ thế giới chức năng. Kiểu gọi hàm này được gọi là phương thức gọi lại. Khi bạn gọi các phương thức này, controle sẽ quay trở lại luồng chính. Ví dụ phương thức gọi lại là các khối trong mục tiêu-C.


yeah, những gì bạn nói phù hợp với sự hiểu biết của tôi sau khi đọc cuốn sách.
Lucas Li

2
Phương thức gọi lại không có gì giống như đa luồng. Phương thức gọi lại chỉ đơn giản là con trỏ hàm (đại biểu). Chủ đề là về chuyển đổi / sử dụng bối cảnh CPU. Một sự tương phản rất đơn giản sẽ là phân luồng là về tối ưu hóa CPU cho UX trong khi các cuộc gọi lại là về chuyển đổi bộ nhớ cho đa hình. Đơn giản vì các luồng có thể thực thi các cuộc gọi lại không có nghĩa là phân luồng và các cuộc gọi lại tương tự nhau. Các luồng cũng thực hiện các phương thức tĩnh trên các lớp tĩnh nhưng các luồng và các kiểu tĩnh không giống nhau.
nghĩa
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.