如何用简单的英语解释回调?它们与从一个函数调用另一个函数从调用函数获取上下文有什么不同?如何向新手程序员解释它们的强大功能?
当前回答
当我们有两个函数,函数a和函数b,如果函数a依赖于函数b。
然后我们调用函数b作为回调函数。这在Spring框架中被广泛使用。
其他回答
当我们有两个函数,函数a和函数b,如果函数a依赖于函数b。
然后我们调用函数b作为回调函数。这在Spring框架中被广泛使用。
回调函数:
我们定义了一个名为callback的回调函数,给它一个参数otherFunction,并在函数体中调用它。
function callback(otherFunction){
otherFunction();
}
当我们调用回调函数时,它需要一个类型为函数的参数,因此我们使用匿名函数调用它。但是,如果实参不是function类型,则会产生错误。
callback(function(){console.log('SUCCESS!')});
callback(1); // error
烤披萨的例子。 烤炉 披萨底,上面有配料 这里,烤箱是回调函数。 披萨的配料是另一个功能。
值得注意的是,不同的披萨原料可以做出不同类型的披萨,但烘焙披萨的烤箱是一样的。 这在某种程度上是回调函数的工作,它不断期望具有不同功能的函数,以产生不同的自定义结果。
现实生活中的例子
这里有一个真实的例子,嗯,我自己的生活。
当我下午5点结束我的工作时,我的待办事项清单上有很多事情:
打电话给兽医要我的狗的检查结果。 遛狗。 处理我的税务问题。 洗碗。 回复私人邮件。 洗衣服。
当我打电话给兽医时,我接到了一个接待员的电话。接待员告诉我,我需要等待兽医,这样兽医就可以向我解释测试结果。接待员想让我等一下,直到兽医准备好。
你对此有什么反应?我知道我的工作多么低效!所以我向接待员提议,当兽医准备好谈话时,他让兽医给我回个电话。这样,我就不用等电话了,我可以做其他的事情。等兽医准备好了,我就可以把其他的事情暂时搁置,和她谈谈。
它和软件有什么关系
我是单螺纹的。我一次只能做一件事。如果我是多线程的,我将能够并行处理多个任务,但不幸的是,我不能这样做。
如果回调不是一个东西,当我遇到异步任务时,它会阻塞。如。当我打电话给兽医时,兽医需要大约15分钟来完成她正在做的事情,然后她才能和我说话。如果没有回调,我在这15分钟内就会被屏蔽。我就只能坐着等,而不能做其他的工作。
下面是没有回调的代码的样子:
function main() {
callVet();
// blocked for 15 minutes
walkDog();
doTaxes();
doDishes();
answerPeronalEmails();
doLaundry();
}
现在用回调:
function main() {
callVet(function vetCallback(vetOnThePhoneReadyToSpeakWithMe) {
talkToVetAboutTestResults(vetOnThePhoneReadyToSpeakWithMe);
});
walkDog();
doTaxes();
doDishes();
answerPeronalEmails();
doLaundry();
}
更一般地说,当您处于单线程执行环境中,并且有某种异步任务时,您可以使用回调以更合乎逻辑的顺序执行事情,而不是让该任务阻塞您的单线程。
一个很好的例子是,如果您有一些前端代码需要发出ajax请求。如。如果您有一个显示用户信息的仪表板。下面是它如何在没有回调的情况下工作。用户将立即看到导航栏,但他们必须等待一段时间才能看到边栏和页脚,因为ajax请求getUser需要一段时间(作为经验法则,网络被认为是很慢的)。
function main() {
displayNavbar();
const user = getUser();
// wait a few seconds for response...
displayUserDashboard(user);
displaySidebar();
displayFooter();
}
现在用回调:
function main() {
displayNavbar();
getUser(function (user) {
displayUserDashboard(user);
});
displaySidebar();
displayFooter();
}
通过利用回调,我们现在可以在ajax请求的响应返回给我们之前显示边栏和页脚。这就好比我对接待员说:“我不想在电话上等15分钟。兽医准备好和我谈谈的时候给我回电话,与此同时,我会继续做我待办事项清单上的其他事情。”在现实生活中,你可能应该更优雅一些,但在编写软件时,你可以对CPU非常粗鲁。
从一个例子开始总是更好的:)。
假设有两个模块A和B。
你希望模块A在模块B中发生某些事件/条件时得到通知。然而,模块B对模块A一无所知。它只知道模块A提供给它的函数指针指向模块A的特定函数的地址。
因此,所有B现在必须做的是,当一个特定的事件/条件发生时,使用函数指针“回调”到模块A。A可以在回调函数内部进行进一步处理。
这里一个明显的优点是,你从模块B中抽象出了模块A的所有内容。模块B不必关心模块A是谁/什么。
想象一下,一个朋友要离开你的家,你告诉她“到家后给我打个电话,好让我知道你已经安全到家了”;这是(字面上的)回电。这就是回调函数,与语言无关。您希望某个过程在完成某些任务后将控制传递回给您,因此您可以给它一个函数,用于回调。
例如,在Python中,
grabDBValue( (lambda x: passValueToGUIWindow(x) ))
grabDBValue可以被编写为只从数据库获取一个值,然后让您指定对该值实际做什么,因此它接受一个函数。您不知道grabDBValue何时或是否会返回,但是如果/当它返回时,您知道希望它做什么。在这里,我传入一个匿名函数(或lambda),它将值发送到GUI窗口。我可以通过这样做轻松地改变程序的行为:
grabDBValue( (lambda x: passToLogger(x) ))
回调在函数是第一类值的语言中工作得很好,就像通常的整数、字符串、布尔值等。在C语言中,你可以通过传递指向函数的指针来“传递”函数,调用者可以使用它;在Java中,调用者将请求具有特定方法名的特定类型的静态类,因为类之外没有函数(实际上是“方法”);在大多数其他动态语言中,您可以通过简单的语法传递函数。
Protip:
在具有词法作用域的语言(如Scheme或Perl)中,您可以使用这样的技巧:
my $var = 2;
my $val = someCallerBackFunction(sub callback { return $var * 3; });
# Perlistas note: I know the sub doesn't need a name, this is for illustration
在本例中$val将为6,因为回调可以访问定义它的词法环境中声明的变量。词法作用域和匿名回调是一个强大的组合,值得新手进一步研究。