什么是回调函数?
由于该死的东西的名称,开发人员经常对回调是什么感到困惑。
回调函数是一个函数,它是:
可由另一个函数访问,并且
如果第一个函数完成,则在第一个函数之后调用
想象回调函数如何工作的一个很好的方法是,它是一个在传递给它的函数的“后面调用”的函数。
也许更好的名字是“调用后”功能。
这个结构对于异步行为非常有用,我们希望在前一个事件完成时发生活动。
伪代码:
// A function which accepts another function as an argument
// (and will automatically invoke that function when it completes - note that there is no explicit call to callbackFunction)
funct printANumber(int number, funct callbackFunction) {
printout("The number you provided is: " + number);
}
// a function which we will use in a driver function as a callback function
funct printFinishMessage() {
printout("I have finished printing numbers.");
}
// Driver method
funct event() {
printANumber(6, printFinishMessage);
}
调用 event() 的结果:
The number you provided is: 6
I have finished printing numbers.
这里的输出顺序很重要。由于之后调用回调函数,“我已完成打印数字”最后打印,而不是第一个打印。
回调之所以被称为是因为它们与指针语言一起使用。如果您不使用其中之一,请不要为“回调”这个名称而努力。请理解,它只是描述作为参数提供给另一个方法的方法的名称,这样当调用父方法时(无论条件如何,例如按钮单击、计时器滴答等)并且其方法主体完成,然后调用回调函数。
一些语言支持支持多个回调函数参数的结构,并根据父函数的完成方式调用(即,如果父函数成功完成,则调用一个回调,如果父函数抛出一个回调,则调用另一个回调。具体错误等)。
不透明的定义
回调函数是您提供给另一段代码的函数,允许该代码调用它。
人为的例子
你为什么想做这个?假设您需要调用一项服务。如果服务立即返回,您只需:
调用它 等待结果 一旦结果进来就继续
例如,假设服务是 factorial
函数。当您需要 5!
的值时,您将调用 factorial(5)
,然后会发生以下步骤:
您当前的执行位置被保存(在堆栈上,但这并不重要)执行被移交给阶乘当阶乘完成时,它将结果放在您可以得到的地方执行回到它在 [1] 中的位置
现在假设 factorial
花了很长时间,因为您给它提供了巨大的数字,并且它需要在某个地方的某个超级计算集群上运行。假设您预计返回结果需要 5 分钟。你可以:
保持你的设计并在晚上睡觉时运行你的程序,这样你就不会有一半的时间盯着屏幕设计你的程序来做其他事情,而阶乘正在做它的事情
如果您选择第二个选项,那么回调可能对您有用。
端到端设计
为了利用回调模式,您需要能够通过以下方式调用 factorial
:
factorial(really_big_number, what_to_do_with_the_result)
第二个参数 what_to_do_with_the_result
是您发送给 factorial
的函数,希望 factorial
在返回之前对其结果调用它。
是的,这意味着需要编写 factorial
以支持回调。
现在假设您希望能够将参数传递给您的回调。现在你不能,因为你不会调用它,factorial
是。因此,需要编写 factorial
以允许您将参数传入,并且它只会在调用它时将它们交给您的回调。它可能看起来像这样:
factorial (number, callback, params)
{
result = number! // i can make up operators in my pseudocode
callback (result, params)
}
现在 factorial
允许这种模式,您的回调可能如下所示:
logIt (number, logger)
{
logger.log(number)
}
您对 factorial
的调用将是
factorial(42, logIt, logger)
如果您想从 logIt
返回一些东西怎么办?嗯,你不能,因为 factorial
没有注意它。
那么,为什么 factorial
不能只返回您的回调返回的内容?
使其无阻塞
由于执行的目的是在 factorial
完成时将其移交给回调,因此它实际上不应该向其调用者返回任何内容。理想情况下,它会以某种方式在另一个线程/进程/机器中启动它的工作并立即返回,以便您可以继续,可能是这样的:
factorial(param_1, param_2, ...)
{
new factorial_worker_task(param_1, param_2, ...);
return;
}
这现在是一个“异步调用”,这意味着当您调用它时,它会立即返回,但还没有真正完成它的工作。所以你确实需要一些机制来检查它,并在它完成时获得它的结果,并且你的程序在这个过程中变得更加复杂。
顺便说一句,使用这种模式,factorial_worker_task
可以异步启动您的回调并立即返回。
所以你会怎么做?
答案是保持在回调模式内。每当你想写
a = f()
g(a)
并且 f
将被异步调用,您将改为编写
f(g)
其中 g
作为回调传递。
这从根本上改变了程序的流拓扑,需要一些时间来适应。
您的编程语言可以通过为您提供一种即时创建函数的方法来帮助您。在上面的代码中,函数 g
可能与 print (2*a+1)
一样小。如果您的语言要求您将其定义为一个单独的函数,并带有完全不必要的名称和签名,那么如果您经常使用这种模式,您的生活将会变得不愉快。
另一方面,如果您的语言允许您创建 lambda,那么您的状态会好得多。然后你最终会写出类似的东西
f( func(a) { print(2*a+1); })
这好多了。
如何传递回调
您将如何将回调函数传递给 factorial
?好吧,你可以通过多种方式做到这一点。
如果被调用的函数在同一个进程中运行,你可以传递一个函数指针或者你可能想在你的程序中维护一个 fn name --> fn ptr 的字典,在这种情况下你可以传递 name 也许你的语言允许你就地定义函数,可能是 lambda!在内部,它正在创建某种对象并传递一个指针,但您不必担心这一点。也许您正在调用的函数在完全独立的机器上运行,并且您正在使用诸如 HTTP 之类的网络协议来调用它。您可以将回调公开为 HTTP 可调用函数,并传递其 URL。
你明白了。
最近回调的兴起
在我们进入的这个网络时代,我们调用的服务通常是通过网络进行的。我们通常对这些服务没有任何控制权,即我们没有编写它们,我们不维护它们,我们无法确保它们正常运行或它们的性能如何。
但是我们不能指望我们的程序在等待这些服务响应时阻塞。意识到这一点,服务提供商经常使用回调模式设计 API。
JavaScript 非常好地支持回调,例如使用 lambda 和闭包。 JavaScript 世界中有很多活动,无论是在浏览器上还是在服务器上。甚至还有针对移动设备开发的 JavaScript 平台。
随着我们向前发展,我们中将有越来越多的人编写异步代码,对此理解将是必不可少的。
Wikipedia 上的 Callback page 很好地解释了这一点:
在计算机编程中,回调是对可执行代码或一段可执行代码的引用,它作为参数传递给其他代码。这允许较低级别的软件层调用在较高级别层中定义的子例程(或函数)。
外行的回应是,它不是由您调用,而是由用户或浏览器在某个事件发生或处理某些代码之后调用的函数。
让我们保持简单。什么是回调函数?
比喻和类比的例子
我有秘书。每天我都会要求她:(i) 将公司寄出的邮件寄到邮局, 她完成之后,去做:(ii) 无论我写什么任务为她在其中一个 sticky notes 上。
现在,便签上的任务是什么?任务每天都在变化。
假设在这一天,我要求她打印一些文件。所以我把它写在便利贴上,然后把它和她需要发布的外发邮件一起钉在她的桌子上。
总之:
首先,她需要放下邮件,完成后,她需要立即打印一些文件。
回调函数是第二个任务:打印这些文档。因为它是在邮件投递之后完成的,而且还因为告诉她打印文件的便签与她需要发布的邮件一起提供给了她。
现在让我们将其与编程词汇联系起来
本例中的方法名称为:DropOffMail。
而回调函数是:PrintOffDocuments。 PrintOffDocuments 是回调函数,因为我们希望秘书这样做,只有在 DropOffMail 运行之后。
所以我会“通过:PrintOffDocuments 作为 DropOffMail 方法的“参数”。这是一个重要的观点。
就是这样。而已。我希望这为您解决了问题 - 如果没有,请发表评论,我会尽力澄清。
回调函数是在满足特定条件时应该调用的函数。回调函数不是立即被调用,而是在未来的某个时间点被调用。
通常,当正在启动一个异步完成的任务时使用它(即,将在调用函数返回后的某个时间完成)。
例如,请求网页的函数可能要求其调用者提供一个回调函数,该回调函数将在网页完成下载时调用。
"...when a condition is met"
但我认为当父函数完成执行并且不依赖于条件(?)时会调用回调。
回调最容易用电话系统来描述。函数调用类似于打电话给某人,问她一个问题,得到答案,然后挂断电话。添加回拨会改变类比,以便在问她问题后,您还可以给她您的姓名和电话号码,以便她可以回电给您答案。
-- Paul Jakubik,“C++ 中的回调实现”
我相信这个“回调”行话在很多地方都被错误地使用了。我的定义是这样的:
回调函数是您传递给某人并让他们在某个时间点调用它的函数。
我认为人们只是阅读了 wiki 定义的第一句话:
回调是对可执行代码或一段可执行代码的引用,它作为参数传递给其他代码。
我一直在使用很多 API,看到各种不好的例子。许多人倾向于将函数指针(对可执行代码的引用)或匿名函数(一段可执行代码)命名为“回调”,如果它们只是函数,为什么需要另一个名称呢?
实际上只有wiki定义中的第二句话揭示了回调函数和普通函数之间的区别:
这允许较低级别的软件层调用在较高级别层中定义的子例程(或函数)。
所以区别在于你将传递函数的对象以及传递函数的调用方式。如果您只是定义一个函数并将其传递给另一个函数并直接在该函数体中调用它,请不要将其称为回调。定义说你传入的函数将被“低级”函数调用。
我希望人们不要在模棱两可的上下文中使用这个词,它不能帮助人们更好地理解,只会更糟。
回调与回调函数
回调是在另一个函数完成执行后执行的函数——因此得名“回调”。
什么是回调函数?
将 Funs(即函数对象)作为参数或返回 Funs 的函数称为高阶函数。
任何作为参数传递的函数都称为回调函数。
回调函数是作为参数传递给另一个函数(我们称这个另一个函数为otherFunction)的函数,回调函数在otherFunction内部被调用(或执行)。
function action(x, y, callback) {
return callback(x, y);
}
function multiplication(x, y) {
return x * y;
}
function addition(x, y) {
return x + y;
}
alert(action(10, 10, multiplication)); // output: 100
alert(action(10, 10, addition)); // output: 20
在 SOA 中,回调允许插件模块从容器/环境访问服务。
这使得回调听起来像是方法末尾的返回语句。
我不确定他们是这样的。
我认为回调实际上是对函数的调用,这是另一个函数被调用和完成的结果。
我还认为回调是为了解决最初的调用,以一种“嘿!你要求的那件事?我已经做到了 - 只是想我会让你知道 - 回到你身边”。
回调函数是您为现有函数/方法指定的函数,在操作完成时调用,需要额外的处理等。
例如,在 Javascript,或更具体的 jQuery 中,您可以指定在动画完成时调用的回调参数。
在 PHP 中,preg_replace_callback()
函数允许您提供将在匹配正则表达式时调用的函数,将匹配的字符串作为参数传递。
Call After 将是一个比愚蠢的名字更好的名字,回调。当或如果在函数中满足条件,则调用另一个函数,即 Call After 函数,该函数作为参数接收。
与其在函数中硬编码内部函数,不如编写一个函数来接受已经编写好的 Call After 函数作为参数。 Call After 可能会根据接收参数的函数中的代码检测到的状态变化而被调用。
https://i.stack.imgur.com/VN9Nj.jpg
主程序使用回调函数名称调用库函数(也可能是系统级函数)。该回调函数可能以多种方式实现。主程序根据需要选择一个回调。
最后,库函数在执行过程中调用回调函数。
这个问题的简单答案是回调函数是通过函数指针调用的函数。如果将函数的指针(地址)作为参数传递给另一个函数,当该指针用于调用它指向的函数时,就说进行了回调
假设我们有一个函数 sort(int *arraytobesorted,void (*algorithmchosen)(void))
,它可以接受一个函数指针作为它的参数,可以在 sort()
的实现中的某个点使用。然后,这里被函数指针 algorithmchosen
寻址的代码被称为 回调函数。
并且看到优势是我们可以选择任何算法,例如:
1. algorithmchosen = bubblesort
2. algorithmchosen = heapsort
3. algorithmchosen = mergesort ...
比如说,已经用原型实现了:
1. `void bubblesort(void)`
2. `void heapsort(void)`
3. `void mergesort(void)` ...
这是用于在面向对象编程中实现多态性的概念
“在计算机编程中,回调是对可执行代码或一段可执行代码的引用,它作为参数传递给其他代码。这允许较低级别的软件层调用在较高级别层中定义的子例程(或函数)。” - 维基百科
使用函数指针在 C 中回调
在 C 中,回调是使用函数指针实现的。函数指针——顾名思义,是指向函数的指针。
例如 int (*ptrFunc) ();
这里,ptrFunc 是一个指向不带参数并返回整数的函数的指针。不要忘记放入括号,否则编译器会假定 ptrFunc 是一个普通函数名,它什么都不带,返回一个指向整数的指针。
这是一些演示函数指针的代码。
#include<stdio.h>
int func(int, int);
int main(void)
{
int result1,result2;
/* declaring a pointer to a function which takes
two int arguments and returns an integer as result */
int (*ptrFunc)(int,int);
/* assigning ptrFunc to func's address */
ptrFunc=func;
/* calling func() through explicit dereference */
result1 = (*ptrFunc)(10,20);
/* calling func() through implicit dereference */
result2 = ptrFunc(10,20);
printf("result1 = %d result2 = %d\n",result1,result2);
return 0;
}
int func(int x, int y)
{
return x+y;
}
现在让我们尝试使用函数指针来理解 C 中回调的概念。
完整的程序有三个文件:callback.c、reg_callback.h 和 reg_callback.c。
/* callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* callback function definition goes here */
void my_callback(void)
{
printf("inside my_callback\n");
}
int main(void)
{
/* initialize function pointer to
my_callback */
callback ptr_my_callback=my_callback;
printf("This is a program demonstrating function callback\n");
/* register our callback function */
register_callback(ptr_my_callback);
printf("back inside main program\n");
return 0;
}
/* reg_callback.h */
typedef void (*callback)(void);
void register_callback(callback ptr_reg_callback);
/* reg_callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* registration goes here */
void register_callback(callback ptr_reg_callback)
{
printf("inside register_callback\n");
/* calling our callback function my_callback */
(*ptr_reg_callback)();
}
如果我们运行这个程序,输出将是
这是一个演示函数回调的程序 my_callback 内部的 register_callback 内部的主程序
上层函数像普通调用一样调用下层函数,回调机制允许下层函数通过指向回调函数的指针调用上层函数。
使用接口在 Java 中回调
Java没有函数指针的概念,通过Interface机制实现Callback机制这里我们声明一个Interface,而不是函数指针,而是声明一个Interface,它有一个方法,当被调用者完成任务时会调用该方法
让我通过一个例子来演示它:
回调接口
public interface Callback
{
public void notify(Result result);
}
调用者或更高级别的类
public Class Caller implements Callback
{
Callee ce = new Callee(this); //pass self to the callee
//Other functionality
//Call the Asynctask
ce.doAsynctask();
public void notify(Result result){
//Got the result after the callee has finished the task
//Can do whatever i want with the result
}
}
被调用者或下层函数
public Class Callee {
Callback cb;
Callee(Callback cb){
this.cb = cb;
}
doAsynctask(){
//do the long running task
//get the result
cb.notify(result);//after the task is completed, notify the caller
}
}
使用 EventListener 模式进行回调
项目清单
此模式用于通知 0 到 n 个观察者/监听者特定任务已完成
项目清单
Callback 机制和 EventListener/Observer 机制的区别在于,在 callback 中,被调用者通知单个调用者,而在 Eventlisener/Observer 中,被调用者可以通知任何对该事件感兴趣的人(通知可能会转到其他部分未触发任务的应用程序)
让我通过一个例子来解释它。
事件接口
public interface Events {
public void clickEvent();
public void longClickEvent();
}
类小部件
package com.som_itsolutions.training.java.exampleeventlistener;
import java.util.ArrayList;
import java.util.Iterator;
public class Widget implements Events{
ArrayList<OnClickEventListener> mClickEventListener = new ArrayList<OnClickEventListener>();
ArrayList<OnLongClickEventListener> mLongClickEventListener = new ArrayList<OnLongClickEventListener>();
@Override
public void clickEvent() {
// TODO Auto-generated method stub
Iterator<OnClickEventListener> it = mClickEventListener.iterator();
while(it.hasNext()){
OnClickEventListener li = it.next();
li.onClick(this);
}
}
@Override
public void longClickEvent() {
// TODO Auto-generated method stub
Iterator<OnLongClickEventListener> it = mLongClickEventListener.iterator();
while(it.hasNext()){
OnLongClickEventListener li = it.next();
li.onLongClick(this);
}
}
public interface OnClickEventListener
{
public void onClick (Widget source);
}
public interface OnLongClickEventListener
{
public void onLongClick (Widget source);
}
public void setOnClickEventListner(OnClickEventListener li){
mClickEventListener.add(li);
}
public void setOnLongClickEventListner(OnLongClickEventListener li){
mLongClickEventListener.add(li);
}
}
类按钮
public class Button extends Widget{
private String mButtonText;
public Button (){
}
public String getButtonText() {
return mButtonText;
}
public void setButtonText(String buttonText) {
this.mButtonText = buttonText;
}
}
类复选框
public class CheckBox extends Widget{
private boolean checked;
public CheckBox() {
checked = false;
}
public boolean isChecked(){
return (checked == true);
}
public void setCheck(boolean checked){
this.checked = checked;
}
}
活动课
包 com.som_itsolutions.training.java.exampleeventlistener;
public class Activity implements Widget.OnClickEventListener
{
public Button mButton;
public CheckBox mCheckBox;
private static Activity mActivityHandler;
public static Activity getActivityHandle(){
return mActivityHandler;
}
public Activity ()
{
mActivityHandler = this;
mButton = new Button();
mButton.setOnClickEventListner(this);
mCheckBox = new CheckBox();
mCheckBox.setOnClickEventListner(this);
}
public void onClick (Widget source)
{
if(source == mButton){
mButton.setButtonText("Thank you for clicking me...");
System.out.println(((Button) mButton).getButtonText());
}
if(source == mCheckBox){
if(mCheckBox.isChecked()==false){
mCheckBox.setCheck(true);
System.out.println("The checkbox is checked...");
}
else{
mCheckBox.setCheck(false);
System.out.println("The checkbox is not checked...");
}
}
}
public void doSomeWork(Widget source){
source.clickEvent();
}
}
其他类
public class OtherClass implements Widget.OnClickEventListener{
Button mButton;
public OtherClass(){
mButton = Activity.getActivityHandle().mButton;
mButton.setOnClickEventListner(this);//interested in the click event //of the button
}
@Override
public void onClick(Widget source) {
if(source == mButton){
System.out.println("Other Class has also received the event notification...");
}
}
主班
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Activity a = new Activity();
OtherClass o = new OtherClass();
a.doSomeWork(a.mButton);
a.doSomeWork(a.mCheckBox);
}
}
从上面的代码可以看出,我们有一个名为 events 的接口,它基本上列出了我们的应用程序可能发生的所有事件。 Widget 类是所有 UI 组件(如 Button、Checkbox)的基类。这些 UI 组件是实际从框架代码接收事件的对象。 Widget 类实现了事件接口,并且它有两个嵌套接口,即 OnClickEventListener 和 OnLongClickEventListener
这两个接口负责监听 Widget 派生的 UI 组件(如 Button 或 Checkbox)上可能发生的事件。因此,如果我们将此示例与前面使用 Java 接口的 Callback 示例进行比较,这两个接口作为 Callback 接口工作。所以上层代码(Here Activity)实现了这两个接口。并且每当一个小部件发生事件时,都会调用更高级别的代码(或在更高级别的代码中实现的这些接口的方法,这里是Activity)。
现在让我讨论一下回调和事件监听器模式之间的基本区别。正如我们提到的,使用回调,被调用者只能通知一个调用者。但是在 EventListener 模式的情况下,应用程序的任何其他部分或类都可以注册可能发生在 Button 或 Checkbox 上的事件。这种类的例子是OtherClass。如果你看OtherClass的代码,你会发现它已经将自己注册为Activity中定义的Button中可能发生的ClickEvent的监听器。有趣的是,除了 Activity(调用者)之外,每当 Button 上发生点击事件时,也会通知这个 OtherClass。
回调是一种将函数作为参数传递给另一个函数并在过程完成后调用该函数的想法。
如果您通过上述很棒的答案了解了回调的概念,我建议您应该了解其想法的背景。
“是什么让他们(计算机科学家)开发回调?”你可能会学到一个问题,那就是阻塞。(尤其是阻塞 UI)并且回调不是唯一的解决方案。还有很多其他的解决方案(例如:Thread、Futures、Promises...)。
回调函数是您传递(作为引用或指针)给某个函数或对象的函数。此函数或对象将在以后的任何时间(可能多次)出于任何目的调用此函数:
通知任务结束
请求两个项目之间的比较(如在 c qsort() 中)
报告进程的进度
通知事件
委托对象的实例化
委派一个区域的绘画
...
因此,将回调描述为在另一个函数或任务结束时调用的函数过于简化(即使它是一个常见的用例)。
一个重要的使用领域是您将其中一个函数注册为句柄(即回调),然后发送消息/调用某个函数来完成某些工作或处理。现在处理完成后,被调用的函数会调用我们注册的函数(即现在回调完成),从而表明我们处理完成。
This wikipedia 链接以图形方式解释得很好。
回调函数,也称为高阶函数,是作为参数传递给另一个函数的函数,回调函数在父函数内部被调用(或执行)。
$("#button_1").click(function() {
alert("button 1 Clicked");
});
这里我们将一个函数作为参数传递给 click 方法。而click方法将调用(或执行)我们传递给它的回调函数。
回调函数 作为参数传递给另一个函数的函数。
function test_function(){
alert("Hello world");
}
setTimeout(test_function, 2000);
注意:在上面的示例中 test_function 用作 setTimeout 函数的参数。
myButton.Click += (sender, args) => SomeFunction();
(在 C# 中)
不定期副业成功案例分享
once its parent method completes, the function which this argument represents is then called
。因此,如果函数作为参数传递给另一个函数,但从父函数的运行时中间调用,如parent(cb) {dostuff1(); cb(); dostuff2()}
,那么它不被视为callback
函数吗?