如何用简单的英语解释回调?它们与从一个函数调用另一个函数从调用函数获取上下文有什么不同?如何向新手程序员解释它们的强大功能?
当前回答
我很震惊地看到这么多聪明的人都没有强调“回调”这个词已经有了两种不一致的用法。
这两种方法都涉及到通过向现有函数传递附加功能(匿名或命名的函数定义)来定制函数。ie。
customizableFunc(customFunctionality)
如果自定义功能只是插入到代码块中,则您已经自定义了该函数,如下所示。
customizableFucn(customFunctionality) {
var data = doSomthing();
customFunctionality(data);
...
}
虽然这种注入的功能通常被称为“回调”,但它并不是偶然的。一个非常明显的例子是forEach方法,其中提供了一个自定义函数作为参数,应用于数组中的每个元素以修改数组。
But this is fundamentally distinct from the use of "callback" functions for asynchronous programming, as in AJAX or node.js or simply in assigning functionality to user interaction events (like mouse clicks). In this case, the whole idea is to wait for a contingent event to occur before executing the custom functionality. This is obvious in the case of user interaction, but is also important in i/o (input/output) processes that can take time, like reading files from disk. This is where the term "callback" makes the most obvious sense. Once an i/o process is started (like asking for a file to be read from disk or a server to return data from an http request) an asynchronous program doesn't wait around for it to finish. It can go ahead with whatever tasks are scheduled next, and only respond with the custom functionality after it has been notified that the read file or http request is completed (or that it failed) and that the data is available to the custom functionality. It's like calling a business on the phone and leaving your "callback" number, so they can call you when someone is available to get back to you. That's better than hanging on the line for who knows how long and not being able to attend to other affairs.
异步使用本质上涉及到一些侦听所需事件的方法(例如,i/o进程的完成),以便当它发生时(且仅当它发生时)执行自定义的“回调”功能。在明显的AJAX示例中,当数据实际从服务器到达时,“回调”函数将被触发,以使用该数据修改DOM,从而重新绘制浏览器窗口。
回顾一下。有些人使用“回调”这个词来指代任何可以作为参数注入到现有函数中的自定义功能。但是,至少对我来说,这个词最合适的用法是异步使用注入的“回调”函数——仅在等待通知的事件发生时执行。
其他回答
现实生活中的例子
这里有一个真实的例子,嗯,我自己的生活。
当我下午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非常粗鲁。
假设你需要一个函数返回10的平方,那么你写一个函数:
function tenSquared() {return 10*10;}
之后你需要9的平方,所以你写了另一个函数:
function nineSquared() {return 9*9;}
最终你将用一个泛型函数替换所有这些:
function square(x) {return x*x;}
同样的想法也适用于回调。你有一个函数,它做一些事情,当完成时调用doA:
function computeA(){
...
doA(result);
}
之后你想要完全相同的函数调用doB,而不是你可以复制整个函数:
function computeB(){
...
doB(result);
}
或者你可以将回调函数作为变量传递,并且只需要使用该函数一次:
function compute(callback){
...
callback(result);
}
然后你只需要调用compute(doA)和compute(doB)。
除了简化代码之外,它还让异步代码通过在完成时调用任意函数来让您知道它已经完成,这与打电话给某人并留下回调号码类似。
通常,我们将变量发送给函数:function1(var1, var2)。
假设,你想在它被作为参数给出之前处理它:
这是一种回调类型,其中function2执行一些代码并将变量返回给初始函数。
编辑:回调这个词最常见的意思是一个函数作为参数传递给另一个函数,并在稍后的时间点被调用。这些思想存在于允许高阶函数的语言中,即将函数视为一等公民,通常用于异步编程中。onready dosomething()。在这里,只有当它准备好了,事情才会发生。
将方法看作是将任务交给同事。一个简单的任务可能如下:
Solve these equations:
x + 2 = y
2 * x = 3 * y
你的同事勤奋地计算了一下,并给出了以下结果:
x = -6
y = -4
但是你的同事有一个问题,他并不总是理解符号,比如^,但是他通过它们的描述理解它们。如指数。每次他找到其中一个,你就会得到以下信息:
I don't understand "^"
这就要求你在向你的同事解释完这个角色的含义后,再重写一遍你的整个指令集,而他并不总是在问题之间记得。而且他也很难记住你的建议,比如直接问我。然而,他总是尽可能地遵循你的书面指示。
你想到了一个解决方案,你只需在所有的指令中添加以下内容:
If you have any questions about symbols, call me at extension 1234 and I will tell you its name.
现在,无论何时他有问题,他都会打电话问你,而不是给你一个糟糕的回复,让整个过程重新开始。
回呼是一个贴了邮票的回邮信封。当你调用一个函数时,就像发送一封信一样。如果您希望该函数调用另一个函数,则以引用或地址的形式提供该信息。