什么是回调函数?
什么是回调函数?
Answers:
由于该死的东西的名字,开发人员常常对回调是什么感到困惑。
回调函数是一个函数,它是:
设想回调函数如何工作的一种好方法是,它是传递给它的函数的“ 后面 ” 调用的函数。
也许更好的名字应该是“ call after”功能。
这种构造对于异步行为非常有用,在异步行为中,我们希望每当前一个事件完成时就进行一次活动。
伪代码:
// 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.
这里的输出顺序很重要。由于回调函数是在以后调用的,因此“我已经完成打印号码”的打印是最后而不是首先打印。
所谓的回调是由于其与指针语言一起使用。如果您不使用其中之一,请不要使用“回调”这个名称。只需了解一下,它只是描述一个方法的名称,该方法作为另一个方法的参数提供,这样,当调用父方法(无论条件如何,例如单击按钮,计时器滴答声等)且其方法主体完成时,然后调用回调函数。
某些语言支持以下结构,其中支持多个回调函数参数,并根据父函数的完成方式进行调用(即,如果父函数成功完成,则调用一个回调,如果父函数抛出一个a,则调用另一个回调。具体错误等)。
once its parent method completes, the function which this argument represents is then called
。因此,如果将函数作为参数传递给另一个函数,但从父函数运行时的中间调用,则该函数parent(cb) {dostuff1(); cb(); dostuff2()}
不被视为callback
函数吗?
回调函数是您提供给另一段代码的功能,允许该代码调用该函数。
你为什么想做这个?假设有一个您需要调用的服务。如果服务立即返回,则您只需:
例如,假设服务是factorial
功能。当您需要的值时5!
,您将调用factorial(5)
,并且将发生以下步骤:
您当前的执行位置已保存(在堆栈上,但这并不重要)
执行移交给 factorial
当factorial
完成时,它会将结果的地方,你可以得到它
执行返回到[1]中的位置
现在假设factorial
花费了很长时间,因为您要给它提供大量的数据,并且它需要在某些超级计算集群中运行。假设您预计需要5分钟才能返回结果。你可以:
保持设计并在晚上入睡时运行程序,这样就不会有一半时间盯着屏幕
设计程序做其他的事情,而factorial
在做它的事
如果选择第二个选项,则回调可能对您有用。
为了利用回调模式,您想要的是能够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
在程序中维护一个字典,在这种情况下,您可以传递名称
也许您的语言允许您就地定义函数,可能是lambda!在内部,它正在创建某种对象并传递一个指针,但是您不必为此担心。
也许您正在调用的函数正在完全独立的机器上运行,并且您正在使用诸如HTTP之类的网络协议来调用它。您可以将回调作为HTTP可调用函数公开,并传递其URL。
你明白了。
在我们进入的这个网络时代,我们调用的服务通常是通过网络进行的。我们通常对这些服务没有任何控制权,即我们没有编写它们,不维护它们,无法确保它们正常运行或性能如何。
但是我们不能期望我们的程序在等待这些服务响应时会阻塞。意识到这一点,服务提供商经常使用回调模式来设计API。
JavaScript非常好地支持回调,例如使用lambda和闭包。在JavaScript世界中,浏览器和服务器上都有很多活动。甚至还有针对移动设备开发的JavaScript平台。
随着我们的前进,越来越多的人将在编写异步代码,对此的理解将至关重要。
请注意,回调是一个词。
维基百科的回调页面对此进行了很好的解释。
维基百科页面的报价:
在计算机编程中,回调是对可执行代码或一段可执行代码的引用,该代码作为参数传递给其他代码。这允许较低层的软件层调用在较高层中定义的子例程(或函数)。
回调函数是在满足特定条件时应调用的函数。回调函数不是立即被调用,而是在将来的某个时刻被调用。
通常,它在启动将异步完成的任务(即将在调用函数返回后的一段时间内完成)时使用。
例如,请求网页的函数可能要求其调用者提供一个回调函数,该函数将在网页下载完成后被调用。
"...when a condition is met"
但我认为当父函数完成执行时便会调用回调,并且回调不依赖于条件(?)。
回叫最容易用电话系统来描述。功能呼叫类似于打电话给某人,问一个问题,得到答案并挂断电话。添加回叫会改变类比,以便在问了她一个问题之后,还给了她您的姓名和电话号码,以便她可以用答案给您回电。
-Paul Jakubik,“ C ++中的回调实现”
我相信这种“回调”行话在很多地方被错误地使用。我的定义是这样的:
回调函数是您传递给某人并让他们在某个时间点调用的函数。
我认为人们只是阅读了Wiki定义的第一句话:
回调是对可执行代码或一段可执行代码的引用,该代码作为参数传递给其他代码。
我一直在使用许多API,请参见各种不良示例。很多人倾向于将函数指针(对可执行代码的引用)或匿名函数(可执行代码的一部分)命名为“回调”,如果它们只是函数,为什么还要为此命名呢?
实际上,Wiki定义中只有第二句话揭示了回调函数和普通函数之间的区别:
这允许较低层的软件层调用在较高层中定义的子例程(或函数)。
所以区别在于您将要传递谁,以及传递的函数将如何被调用。如果仅定义一个函数并将其传递给另一个函数并在该函数主体中直接调用它,则不要将其称为回调。定义说您传入的函数将被“较低级”函数调用。
我希望人们可以在模棱两可的上下文中停止使用此词,它不能帮助人们更好地理解,只会变得更糟。
让我们保持简单。什么是回叫功能?
寓言和类比的例子
我有秘书 我每天都要求她:(i)在邮局放下公司的外发邮件,然后在她这样做之后,去做:(ii)我在其中一张便笺上为她写的任何任务。
现在,便笺上的任务是什么?任务每天都有所不同。
假设在这一天,我要求她打印一些文件。因此,我将其写在便笺上,然后将其与她需要发布的外出邮件一起固定在她的桌子上。
综上所述:
回叫功能是第二个任务:打印这些文档。因为它是在邮件投递之后完成的,而且还因为告诉她打印文档的便条纸与她需要邮寄的邮件一起发送给了她。
现在让我们将其与编程词汇结合起来
仅此而已。而已。希望为您解决问题-如果没有,请发表评论,我会尽力澄清。
这使回调听起来像方法末尾的return语句。
我不确定那是什么。
我认为回调实际上是对函数的调用,这是由于另一个函数被调用并完成的结果。
我还认为回调是为了解决原始调用,它的意思是“嘿!您要的东西?我已经做到了-只是想让您知道-回到您的身边”。
什么是回调?
什么是回调函数?
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中,回调允许插件模块从容器/环境访问服务。
比愚蠢的名字callback更好的名字是Call After。当一个函数满足条件时,或调用另一个函数,即Call After函数,该函数作为参数接收。
而不是硬编码一个函数中的内部函数,而是编写一个函数以接受已经编写的Call After函数作为参数。该呼叫后可能会被称为基于状态变化的函数接收参数检测的代码。
假设我们有一个函数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)();
}
如果我们运行该程序,输出将是
这是一个程序,该程序在主程序内部的register_callback内部的my_callback内部演示函数回调
较高层的函数将较低层的函数作为普通调用来调用,并且回调机制允许较低层的函数通过指向回调函数的指针来调用较高层的函数。
Java使用界面中的回调
Java没有函数指针的概念。它通过其接口机制实现回调机制。在此,我们声明一个接口,该接口具有一个函数,该方法将在被调用者完成其任务时被调用,而不是函数指针。
让我通过一个示例进行演示:
回调接口
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个观察者/侦听器特定任务已完成
回调机制和EventListener / Observer机制之间的区别在于,在回调中,被叫方通知单个调用方,而在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);
}
}
从上面的代码中可以看到,我们有一个称为事件的接口,该接口基本上列出了应用程序可能发生的所有事件。Widget类是所有UI组件(如Button,Checkbox)的基类。这些UI组件是实际从框架代码接收事件的对象。窗口小部件类实现事件接口,并且它具有两个嵌套接口,即OnClickEventListener和OnLongClickEventListener
这两个接口负责侦听在Widget派生的UI组件(例如Button或Checkbox)上可能发生的事件。因此,如果将本示例与使用Java接口的早期Callback示例进行比较,则这两个接口将用作Callback接口。因此,较高级别的代码(此处的活动)实现了这两个接口。而且,每当窗口小部件发生事件时,就会调用更高级别的代码(或在更高级别的代码中实现的这些接口的方法,在这里为Activity)。
现在让我讨论Callback和Eventlistener模式之间的基本区别。正如我们已经提到的,使用回叫,被叫方只能通知一个呼叫者。但是,对于EventListener模式,应用程序的任何其他部分或类都可以注册按钮或复选框上可能发生的事件。此类的示例是OtherClass。如果您看到OtherClass的代码,则会发现它已将自己注册为ClickEvent的侦听器,该事件可能在Activity中定义的Button中发生。有趣的是,除了活动(调用方)之外,只要在Button上发生click事件,此OtherClass也将得到通知。
一个重要的用法领域是,您将一个函数注册为一个句柄(即回调),然后发送消息/调用某些函数以完成某些工作或处理。现在,在处理完成之后,被调用函数将调用我们的注册函数(即,现在已完成回调),从而指示我们处理已完成。
这个 Wikipedia链接用图形很好地解释了。
回调函数,也称为高阶函数,是一个作为参数传递给另一个函数的函数,并且该回调函数在父函数内部被调用(或执行)。
$("#button_1").click(function() {
alert("button 1 Clicked");
});
在这里,我们将一个函数作为参数传递给click方法。click方法将调用(或执行)我们传递给它的回调函数。