什么是回调函数?
当前回答
维基百科上的回调页面很好地解释了这一点:
在计算机编程中,回调是对可执行代码或一段可执行代码的引用,作为参数传递给其他代码。这允许较低级别的软件层调用在较高级别的层中定义的子程序(或函数)。
其他回答
回调函数也称为高阶函数,是作为参数传递给另一个函数的函数,回调函数在父函数内调用(或执行)。
$("#button_1").click(function() {
alert("button 1 Clicked");
});
这里我们将一个函数作为参数传递给click方法。click方法将调用(或执行)我们传递给它的回调函数。
不透明定义
回调函数是您提供给另一段代码的函数,允许该代码调用它。
有争议的例子
你为什么要这样做?假设您需要调用一个服务。如果服务立即返回,您只需:
叫它吧等待结果结果出来后继续
例如,假设服务是阶乘函数。当您希望值为5!,您将调用factorial(5),并执行以下步骤:
当前执行位置已保存(在堆栈上,但这并不重要)执行移交给阶乘当阶乘完成时,它会将结果放在你可以得到的地方执行恢复到[1]中的位置
现在假设阶乘花费了很长时间,因为你给了它巨大的数字,它需要在一些超级计算集群上运行。假设您预计需要5分钟才能返回结果。您可以:
保持你的设计并在晚上睡觉时运行你的程序,这样你就不会一半时间盯着屏幕设计你的程序,让它在factorial做它的时候做其他的事情
如果您选择第二个选项,则回调可能对您有效。
端到端设计
为了利用回调模式,您需要能够以以下方式调用factorial:
factorial(really_big_number, what_to_do_with_the_result)
第二个参数what_to_do_with_The_result是一个随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(42, logIt, logger)
如果你想从logIt中返回什么呢?你不能,因为阶乘没有注意到它。
那么,为什么阶乘不能只返回回调返回的值?
使其无阻塞
由于执行是在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名称-->fn ptr的字典,在这种情况下,您可以传递名称也许您的语言允许您就地定义函数,可能是lambda!在内部,它创建某种对象并传递指针,但您不必担心这一点。也许您正在调用的函数是在一台完全独立的机器上运行的,并且您正在使用HTTP之类的网络协议来调用它。您可以将回调公开为HTTP可调用函数,并传递其URL。
你明白了。
最近出现的回调
在我们进入的这个网络时代,我们调用的服务通常是通过网络进行的。我们通常无法控制这些服务,即我们没有编写它们,我们没有维护它们,我们无法确保它们正常运行或如何运行。
但我们不能期望我们的程序在等待这些服务响应时被阻止。意识到这一点,服务提供商通常使用回调模式设计API。
JavaScript非常好地支持回调,例如使用lambdas和闭包。JavaScript世界中有很多活动,无论是在浏览器上还是在服务器上。甚至还有JavaScript平台正在为移动设备开发。
随着我们的前进,我们中越来越多的人将编写异步代码,对此理解至关重要。
让我们保持简单。什么是回调函数?
抛物线和类比示例
我有一个秘书。每天我都会让她:(I)把公司的邮件寄到邮局,等她寄完之后,再做:(ii)我在那些便笺上为她写的任何任务。
现在,便笺上的任务是什么?任务每天都不同。
假设在这一天,我要求她打印一些文件。所以我把它写在便笺上,然后把它和她需要寄的邮件一起钉在她的桌子上。
总而言之:
首先,她需要放下邮件完成后,她需要立即打印一些文件。
回调函数是第二项任务:打印这些文档。因为这是在邮件投递后完成的,同时也是因为通知她打印文档的便笺和她需要邮寄的邮件一起给了她。
现在让我们将其与编程词汇联系起来
本例中的方法名为:DropOffMail。回调函数是:PrintOffDocuments。PrintOffDocuments是回调函数,因为我们希望秘书只有在DropOffMail运行后才能这样做。因此,我会将PrintOffDocuments作为“参数”传递给DropOffMail方法。这是一个重要的点。
仅此而已,没什么了。我希望这能为你澄清——如果没有,请发表评论,我会尽力澄清。
维基百科上的回调页面很好地解释了这一点:
在计算机编程中,回调是对可执行代码或一段可执行代码的引用,作为参数传递给其他代码。这允许较低级别的软件层调用在较高级别的层中定义的子程序(或函数)。
开发人员常常因为该死的东西的名称而被什么是回调弄糊涂。
回调函数是一个函数,它是:
可由其他功能访问,以及在第一个函数完成后调用
想象回调函数如何工作的一个很好的方法是,它是一个“在传入函数的后面调用”的函数。
也许一个更好的名称是“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.
这里的输出顺序很重要。由于回调函数是在后面调用的,所以“我已经完成了数字打印”是最后一个,而不是第一个。
回调是所谓的,因为它们与指针语言一起使用。如果你不使用其中一个,就不要为“回调”这个名字而烦恼。只需理解,它只是一个名称,用来描述作为另一个方法的参数提供的方法,这样当调用父方法(无论什么条件,如按钮单击、计时器滴答声等)且其方法体完成时,就会调用回调函数。
某些语言支持支持多个回调函数参数的构造,并根据父函数的完成方式进行调用(即,在父函数成功完成的情况下调用一个回调,在父功能抛出特定错误的情况下,调用另一个回调等)。