什么是回调函数?

问题描述 投票:622回答:21

什么是回调函数?

language-agnostic callback
21个回答
618
投票

由于该死的事物的名称,开发人员常常被回调所困惑。

回调函数是一个函数,它是:

  • 可以通过其他功能访问,以及
  • 如果第一个函数完成,则在第一个函数之后调用

想象回调函数如何工作的一种好方法是它是一个函数,它被传递到函数的“后面”。

也许一个更好的名字将是“追逐后”功能。

此构造对于异步行为非常有用,我们希望在上一个事件完成时发生活动。

伪代码:

// 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.

此处输出的顺序很重要。由于之后调用了回调函数,因此最后打印“我已完成打印数字”,而不是先打印。

回调是所谓的,因为它们使用指针语言。如果您不使用其中一个,请不要使用名称“回调”。只是理解它只是一个名称来描述一个方法,它作为另一个方法的参数提供,这样当调用父方法时(无论条件,如按钮点击,计时器滴答等)及其方法体完成,然后调用回调函数。

有些语言支持支持多个回调函数参数的构造,并根据父函数的完成方式调用(即在父函数成功完成的情况下调用一个回调,在父函数抛​​出一个函数时调用另一个回调)具体错误等)。


16
投票

Call After会比愚蠢的名字,回调更好。当函数内或满足条件时,调用另一个函数,Call After函数,作为参数接收的函数。

不是在函数内硬编码内部函数,而是编写一个函数来接受已经写入的Call After函数作为参数。可以根据接收参数的函数中的代码检测到的状态更改来调用Call After。


15
投票

回调函数是您为现有函数/方法指定的函数,在操作完成时调用,需要额外处理等。

例如,在Javascript中,或者更具体地说是jQuery,您可以指定在动画完成时调用的回调参数。

在PHP中,preg_replace_callback()函数允许您提供在匹配正则表达式时调用的函数,并将匹配的字符串作为参数传递。


14
投票

什么是回调?

  • 一般情况下,通过电话回复有人收到的电话。
  • 在计算中,回调是一段可执行代码,作为参数传递给其他代码。当函数完成它的工作时(或当某个事件发生时),它会调用你的回调函数(它会回调你的名字)函数。

什么是回调函数?

  • 回调函数就像一个仆人,当他完成一项任务时“回调”给他的主人。
  • 回调函数是一个传递给另一个函数的函数(让我们调用另一个函数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中,回调允许插件模块从容器/环境访问服务。

Analogy: Callbacks. Asynchronous. Non-blocking Real life example for callback


10
投票

看图像:)

主程序调用库函数(也可能是系统级函数)和回调函数名。此回调函数可能以多种方式实现。主程序根据需要选择一个回调。

最后,库函数在执行期间调用回调函数。


6
投票

这个问题的简单答案是回调函数是通过函数指针调用的函数。如果将函数的指针(地址)作为参数传递给另一个函数,当该指针用于调用它指向的函数时,则表示回调是


6
投票

假设我们有一个函数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)`   ...

这是用于在面向对象编程中实现多态性的概念


4
投票

“在计算机编程中,回调是对可执行代码或一段可执行代码的引用,它作为参数传递给其他代码。这允许较低级别的软件层调用更高级别层中定义的子例程(或函数)。“ - Wikipedia

使用函数指针在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的概念。

完整的程序有三个文件: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没有函数指针的概念它通过它的接口机​​制实现了回调机制这里我们声明一个接口,它有一个方法,当被调用者完成它的任务时会调用它来代替函数指针

让我通过一个例子来证明:

回调接口

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机制之间的区别在于,在回调中,被调用者通知单个调用者,而在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;
}
}

活动类

package 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类实现了Events接口,它还有两个嵌套接口,即OnClickEventListener和OnLongClickEventListener

这两个接口负责监听Widget派生的UI组件(如Button或Checkbox)上可能发生的事件。因此,如果我们将此示例与使用Java接口的早期Callback示例进行比较,则这两个接口将用作Callback接口。因此,更高级别的代码(Here Activity)实现了这两个接口。每当窗口小部件发生事件时,将调用更高级别的代码(或更高级代码中实现的这些接口的方法,即此处为Activity)。

现在让我讨论Callback和Eventlistener模式之间的基本区别。正如我们提到的那样,使用Callback,Callee只能通知一个呼叫者。但是在EventListener模式的情况下,Application的任何其他部分或类都可以注册Button或Checkbox上可能发生的事件。这类的例子是OtherClass。如果您看到OtherClass的代码,您会发现它已将自身注册为ClickEvent的侦听器,该侦听器可能出现在Activity中定义的Button中。有趣的是,除了Activity(调用者)之外,每当Button上发生click事件时,也会通知此OtherClass。


2
投票

回调函数作为参数传递给另一个函数的函数。

function test_function(){       
 alert("Hello world");  
} 

setTimeout(test_function, 2000);

注意:在上面的示例中,test_function用作setTimeout函数的参数。


2
投票

回调函数是您将(作为引用或指针)传递给某个函数或对象的函数。出于任何目的,此函数或对象将在以后任何时候(可能多次)调用此函数:

  • 通知任务结束
  • 请求两个项目之间的比较(如c qsort())
  • 报告流程的进度
  • 通知事件
  • 委托对象的实例化
  • 委派一个区域的绘画

...

因此将回调描述为在另一个函数或任务结束时调用的函数过度简化(即使它是一个常见的用例)。


1
投票

一个重要的使用区域是您将一个函数注册为句柄(即回调),然后发送消息/调用某些函数来执行某些工作或处理。现在处理完成后,被调用的函数将调用我们的注册函数(即现在回调已完成),从而表明我们已完成处理。 This维基百科链接以图形方式解释得很好。


208
投票

Opaque Definition

回调函数是您提供给另一段代码的函数,允许该代码调用它。

Contrived example

你为什么想做这个?假设您需要调用一个服务。如果服务立即返回,您只需:

  1. 叫它
  2. 等待结果
  3. 结果进入后继续

例如,假设服务是factorial函数。当你想要5!的值时,你会调用factorial(5),并且会发生以下步骤:

  1. 您当前的执行位置已保存(在堆栈中,但这并不重要)
  2. 执行被移交给factorial
  3. factorial完成时,它会将结果放在您可以获得的位置
  4. 执行回到[1]中的位置

现在假设factorial花了很长时间,因为你给它巨大的数字,它需要在一些超级计算集群上运行。假设您需要5分钟才能返回结果。你可以:

  1. 保持你的设计并在你睡着的时候运行你的程序,这样你就不会在一半的时候盯着屏幕
  2. 设计你的程序做其他事情,而factorial正在做的事情

如果您选择第二个选项,则回调可能对您有用。

End-to-end design

为了利用回调模式,您想要的是能够以下列方式调用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)一样小。如果您的语言要求您将其定义为单独的功能,并且具有完全不必要的名称和签名,那么如果您经常使用此模式,您的生活将会变得令人不愉快。

另一方面,如果你的语言允许你创建lambdas,那么你的形状要好得多。然后你会写出类似的东西

f( func(a) { print(2*a+1); })

哪个好多了

How to pass the callback

你如何将回调函数传递给factorial?好吧,你可以通过多种方式实现这一目标。

  1. 如果被调用的函数在同一进程中运行,则可以传递一个函数指针
  2. 或者您可能想在程序中维护fn name --> fn ptr字典,在这种情况下您可以传递名称
  3. 也许您的语言允许您就地定义函数,可能作为lambda!在内部,它正在创建某种对象并传递指针,但您不必担心这一点。
  4. 也许你正在调用的函数是在一个完全独立的机器上运行,而你使用像HTTP这样的网络协议来调用它。您可以将回调公开为HTTP可调用函数,并传递其URL。

你明白了。

The recent rise of callbacks

在我们进入的这个网络时代,我们调用的服务通常是通过网络进行的。我们通常对这些服务没有任何控制权,即我们没有对它们进行编写,我们没有对它们进行维护,我们无法确保它们已经启动或者它们的执行方式。

但是,当我们等待这些服务作出响应时,我们不能指望我们的程序会阻塞。意识到这一点,服务提供商通常使用回调模式设计API。

JavaScript非常好地支持回调,例如与lambdas和闭合。 JavaScript世界中有很多活动,无论是在浏览器上还是在服务器上。甚至还有针对移动设备开发的JavaScript平台。

随着我们向前发展,越来越多的人将编写异步代码,这对于这种理解至关重要。


1
投票

回调函数(也称为高阶函数)是作为参数传递给另一个函数的函数,并且在父函数内调用(或执行)回调函数。

$("#button_1").click(function() {
  alert("button 1 Clicked");
});

这里我们将一个函数作为参数传递给click方法。 click方法将调用(或执行)我们传递给它的回调函数。


1
投票

回调是将函数作为参数传递给另一个函数的想法,并在进程完成后调用此函数。

如果您通过上面的精彩答案获得回调的概念,我建议您应该了解其想法的背景。

“是什么让他们(计算机科学家)发展回调?”您可能会遇到阻塞的问题。(特别是阻止UI)回调并不是唯一的解决方案。还有很多其他解决方案(例如:线程,期货,承诺......)。


90
投票

请注意,回调是一个单词。

The wikipedia callback page解释得非常好。

从维基百科页面引用:

在计算机编程中,回调是对可执行代码或一段可执行代码的引用,它作为参数传递给其他代码。这允许较低级别的软件层调用在较高级别层中定义的子例程(或函数)。


42
投票

外行响应将是一个函数不是由您调用,而是由某个事件发生后或某些代码处理后由用户或浏览器调用。


40
投票

回调函数是在满足某个条件时应该调用的函数。不是立即调用,而是在将来的某个点调用回调函数。

通常,它在启动任务时使用,它将异步完成(即在调用函数返回后将完成一段时间)。

例如,请求网页的功能可能要求其调用者提供将在网页下载完成时调用的回调函数。


33
投票

我相信这种“回调”术语在很多地方被错误地使用了。我的定义是这样的:

回调函数是一个函数,您传递给某人并让他们在某个时间点调用它。

我想人们只是阅读维基定义的第一句话:

回调是对可执行代码或一段可执行代码的引用,它作为参数传递给其他代码。

我一直在使用大量的API,看到各种不好的例子。许多人倾向于命名一个函数指针(对可执行代码的引用)或匿名函数(一段可执行代码)“回调”,如果它们只是函数,为什么你需要另一个名字?

实际上只有wiki定义中的第二句显示了回调函数和普通函数之间的差异:

这允许较低级别的软件层调用在较高级别层中定义的子例程(或函数)。

所以区别在于你要传递函数的人以及传入函数的调用方式。如果您只是定义一个函数并将其传递给另一个函数并直接在该函数体中调用它,请不要将其称为回调函数。定义说你的传入函数将被“低级”函数调用。

我希望人们可以在模棱两可的语境中停止使用这个词,它不能帮助人们更好地理解。


30
投票

根据电话系统最容易描述回叫。功能调用类似于通过电话呼叫某人,向她询问问题,获得答案以及挂断电话;添加回调更改了类比,以便在向她询问问题后,您还会告诉她您的姓名和号码,以便她可以给您回复答案。

- Paul Jakubik,“C ++中的回调实现”


28
投票

让我们保持简单。什么是回拨功能?

通过寓言和比喻的例子

我有一个秘书。我每天都要求她:(i)在邮局放下公司的外发邮件,并在她完成后,做:(ii)我在其中一个sticky notes上为她写的任何任务。

现在,粘滞便笺上的任务是什么?任务每天都在变化。

假设在这个特殊的日子里,我要她打印一些文件。所以我把它写在便利贴上,然后把它和她需要发送的外发邮件一起放在她的桌子上。

综上所述:

  1. 首先,她需要放下邮件
  2. 在完成之后,她需要立即打印一些文件。

回调功能是第二个任务:打印掉那些文件。因为它是在邮件被丢弃之后完成的,并且还因为告诉她打印文档的粘滞便笺与她需要发布的邮件一起提供给她。

现在让我们将其与编程词汇联系起来

  • 在这种情况下,方法名称是:DropOffMail。
  • 回调函数是:PrintOffDocuments。 PrintOffDocuments是回调函数,因为我们希望秘书只在DropOffMail运行之后才这样做。
  • 所以我会将“PrintOffDocuments作为”参数传递给DropOffMail方法。这是一个重点。

这就是全部。而已。我希望能为你清理它 - 如果没有,发表评论,我会尽力澄清。


17
投票

这使得回调听起来像方法结束时的return语句。

我不确定他们是什么。

我认为Callbacks实际上是对函数的调用,因为调用和完成了另一个函数。

我也认为Callbacks是为了解决原始的调用问题,有点“嘿!你要求的那个东西?我已经完成了 - 只是想我会让你知道 - 回到你身边”。

© www.soinside.com 2019 - 2024. All rights reserved.