8bi*_*kie 647

由于该死的东西的名称,开发人员常常被回调所困惑.

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

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

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

也许一个更好的名字将是"追逐后"功能.

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

伪代码:

// 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);
}
Run Code Online (Sandbox Code Playgroud)

调用event()的结果:

The number you provided is: 6
I have finished printing numbers.
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 你的例子很棒,但我不明白为什么术语是"回调".什么时候意味着生命"被召回"? (31认同)
  • @ 8bitjunkie谢谢 - 但是printANumber函数中调用的meaningOfLife方法在哪里? (12认同)
  • 这根本不是真的:“在第一个函数完成后自动调用”。回调根本不需要执行,更不用说自动执行了。事实上,回调在父函数完成之前完成的情况并不少见。我非常不喜欢人们如何将回调描述为“稍后”执行的函数。对于正在了解它们的人来说,这非常令人困惑。简单地说,回调只是作为参数传递给其他函数的函数。时期。更好的解释包括解释为什么回调函数引用。 (6认同)
  • 你好,关于`一旦它的父方法完成,这个参数所代表的函数然后被调用`.因此,如果函数作为参数传递给另一个函数,但是从父函数的运行时调用,如`parent(cb){dostuff1(); CB(); dostuff2()}`那么它不被认为是`callback`函数? (4认同)
  • @MaxYari:恕我直言,它仍然被视为回调。这里重要的是父函数将以某种方式使用输入函数(又名回调)。它可以在中间或最后或满足条件时调用。 (3认同)

Roh*_*jee 216

不透明的定义

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

举例说明

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

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

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

  1. 您当前的执行位置已保存(在堆栈中,但这并不重要)

  2. 执行被移交给 factorial

  3. factorial完成时,它会将结果的地方,你可以得到它

  4. 执行回到[1]中的位置

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

  1. 保持你的设计,并在你睡着的时候晚上运行你的程序,这样你就不会在一半时间盯着屏幕

  2. 设计程序做其他的事情,而factorial在做它的事

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

端到端设计

为了利用回调模式,您希望能够以factorial下列方式调用:

factorial(really_big_number, what_to_do_with_the_result)
Run Code Online (Sandbox Code Playgroud)

第二个参数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)
}
Run Code Online (Sandbox Code Playgroud)

现在factorial允许此模式,您的回调可能如下所示:

logIt (number, logger)
{
    logger.log(number)
}
Run Code Online (Sandbox Code Playgroud)

你的电话factorial会是

factorial(42, logIt, logger)
Run Code Online (Sandbox Code Playgroud)

如果你想从中归还什么logIt怎么办?嗯,你不能,因为factorial没有注意它.

好吧,为什么不能factorial只返回你的回调返回的内容?

使其无阻塞

由于执行意味着在factorial完成时被移交给回调,所以它实际上不应该向其调用者返回任何内容.理想情况下,它会以某种方式在另一个线程/进程/机器中启动它的工作并立即返回,以便您可以继续,可能是这样的:

factorial(param_1, param_2, ...)
{
    new factorial_worker_task(param_1, param_2, ...);
    return;
}
Run Code Online (Sandbox Code Playgroud)

现在这是一个"异步调用",这意味着当你调用它时,它会立即返回,但还没有真正完成它的工作.因此,您确实需要机制来检查它,并在完成后获得结果,并且您的程序在此过程中变得更加复杂.

顺便说一句,使用这种模式,factorial_worker_task可以异步启动回调并立即返回.

所以你会怎么做?

答案是保持回调模式.每当你想写

a = f()
g(a)
Run Code Online (Sandbox Code Playgroud)

并且f将被异步调用,您将改为编写

f(g)
Run Code Online (Sandbox Code Playgroud)

where g作为回调传递.

这从根本上改变了程序的流程拓扑结构,并且需要一些时间来适应.

通过为您提供即时创建功能的方法,您的编程语言可以为您提供很多帮助.在上面的代码中,函数g可能小到print (2*a+1).如果您的语言要求您将其定义为单独的功能,并且具有完全不必要的名称和签名,那么如果您经常使用此模式,您的生活将会变得令人不愉快.

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

f( func(a) { print(2*a+1); })
Run Code Online (Sandbox Code Playgroud)

哪个好多了

如何传递回调

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

  1. 如果被调用的函数在同一进程中运行,则可以传递一个函数指针

  2. 或者您可能希望fn name --> fn ptr在程序中维护字典,在这种情况下您可以传递名称

  3. 也许您的语言允许您就地定义函数,可能作为lambda!在内部,它正在创建某种对象并传递指针,但您不必担心这一点.

  4. 也许你正在调用的函数是在一个完全独立的机器上运行,而你使用像HTTP这样的网络协议来调用它.您可以将回调公开为HTTP可调用函数,并传递其URL.

你明白了.

近期回调上涨

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

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

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

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

  • 完美解释,一切都解释了.我希望我能再次投票. (3认同)

dan*_*nio 91

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

维基百科回调页面很好地解释了它.

从维基百科页面引用:

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

  • 这可能是一个评论 - 基本上它是维基百科的链接 (20认同)
  • 提出答案的好方法. (13认同)
  • 这也导致了不同的答案。名词“回调”是指已经“回调”的东西,就像经过关闭的东西已经被关闭,用于登录的东西是登录。 (4认同)
  • 维基百科实际上已经在其宝库中获得了一些非常棒的编程内容。我一直觉得“回拨”一词最好用“我要回拨到……”这句话来解释。 (2认同)

小智 45

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


Tho*_*att 41

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

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

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


Zan*_* XY 33

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

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

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

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

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

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

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

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

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

  • @Zane Wong ::在最后你写的"定义说你的传入函数将被"低级"函数调用." 你能解释下级功能的含义吗?如果举个例子,它会更好. (3认同)
  • 你的答案是有道理的......但我在描绘它时遇到了麻烦.你能给我举个例子吗? (2认同)

Dej*_*kic 33

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

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


BKS*_*eon 28

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

通过寓言和比喻的例子

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

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

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

综上所述:

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

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

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

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

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


小智 17

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

我不确定他们是什么.

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

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

  • 很好的答案 - 与许多其他答案不同,帮助我理解它! (2认同)

小智 17

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

不是在函数内硬编码内部函数,而是编写一个函数来接受已经编写的Call After函数作为参数.在通话结束后可能会基于功能接收参数的代码检测状态变化被调用.


Pre*_*raj 17

什么是回调

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

什么是回调函数

  • 回调函数就像一个仆人,当他完成一项任务时"回调"给他的主人.
  • 一个回调函数是传递给另一个函数的函数(我们称之为其他功能otherFunction)作为参数,并且回调函数被调用(或执行)内otherFunction.

    function action(x,y,callback){return callback(x,y); }

    函数乘法(x,y){return x*y; }

    函数加法(x,y){return x + y; }

    警报(动作(10,10,乘法)); //输出:100

    警报(动作(10,10,加)); //输出:20

在SOA中,回调允许插件模块从容器/环境访问服务.

打个比方:回调.异步.非阻塞
回调的实际例子

  • 回调函数本身并不是高阶函数。它被传递给高阶函数。 (2认同)

ale*_*lex 15

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

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

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


小智 10

看图像:)这是它的工作原理

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

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

  • 你还介意给它添加一个_text_解释吗?如果图像消失,这个答案会丢失所有上下文. (7认同)

Zai*_*Ali 7

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


Nee*_*raj 6

假设我们有一个函数sort(int *arraytobesorted,void (*algorithmchosen)(void)),它可以接受一个函数指针作为它的参数,可以在实现的某个时候使用它sort().然后,这里函数指针正在寻址的代码algorithmchosen被称为回调函数.

并且看到的优势是我们可以选择任何算法:

  1.    algorithmchosen = bubblesort
  2.    algorithmchosen = heapsort
  3.    algorithmchosen = mergesort   ...
Run Code Online (Sandbox Code Playgroud)

比如,已经使用原型实现了:

  1.   `void bubblesort(void)`
  2.   `void heapsort(void)`
  3.   `void mergesort(void)`   ...
Run Code Online (Sandbox Code Playgroud)

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