如何用通俗易懂的英语解释回调?它们与从一个函数调用另一个函数有何不同?

How to explain callbacks in plain english? How are they different from calling one function from another function?

提问人:Yahoo-Me 提问时间:3/7/2012 最后编辑:Mario GalicYahoo-Me 更新时间:11/17/2022 访问量:215287

问:

如何用通俗易懂的英语解释回调?它们与从调用函数中获取一些上下文的另一个函数调用一个函数有何不同?如何向新手程序员解释它们的力量?

函数 回调 与语言无关 的术语

评论

2赞 ming_codes 3/11/2012
我相信它的学名是延续传递风格。您可以在 wiki 上搜索此内容。
5赞 dbr 3/11/2012
Quora上也有一些关于相同问题的好答案
5赞 moodywoody 3/12/2012
相关问题:stackoverflow.com/questions/824234/what-is-a-callback-function
1赞 sss 6/16/2019
我发现的回调的最好解释 youtube.com/watch?v=xHneyv38Jro

答:

8赞 Nishant 3/7/2012 #1

通常,我们将变量发送到函数: .function1(var1, var2)

假设,您希望在将其作为参数给出之前对其进行处理:function1(var1, function2(var2))

这是一种类型的回调,其中执行一些代码并将变量返回给初始函数。function2

编辑:这个词最常见的含义是一个函数,它作为参数传递给另一个函数,并在以后的时间点被调用。这些是在允许高阶函数的语言中发现的思想,即将函数视为一等公民,并且通常用于编程。.只有当它准备好时才会发生。callbackasynconready(dosomething)dosomething

评论

2赞 johnny 4/21/2014
另一种类型的回调是什么?
0赞 Nishant 1/24/2015
@johnny:Ajax完成时会触发正常的浏览器回调等。
24赞 Gargi Srinivas 3/7/2012 #2

最好从:)示例开始。

假设您有两个模块 A 和 B。

您希望在模块 B 中发生某些事件/条件时通知模块 A。但是,模块 B 对模块 A 一无所知。它所知道的只是通过模块 A 提供给它的功能指针到特定函数(模块 A)的地址。

因此,B 现在要做的就是在使用函数指针发生特定事件/条件时“回调”到模块 A 中。A 可以在回调函数内部进行进一步的处理。

*) 这里的一个明显优点是,你从模块 B 中抽象出关于模块 A 的所有信息,模块 B 不必关心模块 A 是谁/是什么。

评论

0赞 U.Savas 4/21/2020
那么,A 中函数的参数在模块 B 中提供了,这是正确的吗?
1赞 Balaswamy Vaddeman 3/7/2012 #3

[已编辑]当我们有两个函数时,说函数A和函数B,如果函数A依赖于函数B

然后我们调用functionB作为回调函数,这在Spring框架中被广泛使用。

callback function wikipedia example

126赞 Niraj Nawanit 3/10/2012 #4

通常,应用程序需要根据其上下文/状态执行不同的功能。为此,我们使用一个变量来存储有关要调用的函数的信息。‪According to its need the application will set this variable with the information about function to be called and will call the function using the same variable.

在 javascript 中,示例如下。在这里,我们使用方法参数作为变量,在其中存储有关函数的信息。

function processArray(arr, callback) {
    var resultArr = new Array(); 
    for (var i = arr.length-1; i >= 0; i--)
        resultArr[i] = callback(arr[i]);
    return resultArr;
}

var arr = [1, 2, 3, 4];
var arrReturned = processArray(arr, function(arg) {return arg * -1;});
// arrReturned would be [-1, -2, -3, -4]

评论

20赞 Eric 3/11/2014
虽然从技术上讲这是一个回调,但给出的解释听起来与一般函数指针没有区别。它有助于包括一些理由来说明为什么可以使用回调。
5赞 Abhishek Singh 10/16/2015
我不明白这个答案。它能比代码更多的是解释吗?
0赞 Abhi 6/25/2016
为什么我们不能在函数中的回调函数()中执行我们所做的事情function(arg)processArray(arr,callback)
1赞 Dung 11/1/2017
@JoSmo你说对了一部分。将变量 + call-back-function 作为参数传递,获取原始函数使用该变量创建的结果,并将其传递到 call-back-function 中进行进一步处理。示例:func1(a, callback_func){ v = a + 1} 并且有预定义的回调函数:callback_func(v){return v+1;} 这将使 A 增加 2,因此如果您在 “a” 参数中传递整数 4 的参数,则callback_funct将返回 6 作为结果。阅读这个 callbackhell.com 我找到的最佳来源。
0赞 Arjun Kalidas 6/5/2019
这个答案尚不清楚。可以更简单、更清晰!
12赞 effigy 3/11/2012 #5

你感觉不舒服,所以你去看医生。他会检查您并确定您需要一些药物。他开了一些药,然后把处方叫到你当地的药房。你回家了。后来,您的药房打电话告诉您您的处方已准备好。你去捡它。

评论

1赞 a20 6/4/2014
很好的比喻。您能否进一步扩展,也许用一些(简单的)编程相关示例?
1赞 letuboy 3/11/2012 #6

回调允许您将自己的代码插入到另一个代码块中,以便在另一个时间执行,从而修改或添加该其他代码块的行为以满足您的需求。您可以获得灵活性和可定制性,同时能够拥有更可维护的代码。

更少的硬代码 = 更易于维护和更改 = 更少的时间 = 更多的业务价值 = 很棒。

例如,在 javascript 中,使用 Underscore.js,您可以在数组中找到所有偶数元素,如下所示:

var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [2, 4, 6]

示例由 Underscore.js: http://documentcloud.github.com/underscore/#filter 提供

5赞 gatlin 3/11/2012 #7

想象一下,一个朋友要离开你的家,你告诉她“回家后给我打电话,这样我就知道你安全到达了”;这是(字面意思)回电。这就是回调函数,无论语言如何。你希望某个过程在完成某个任务后将控制权传回给你,所以你给它一个函数来回调给你。

例如,在 Python 中,

grabDBValue( (lambda x: passValueToGUIWindow(x) ))

grabDBValue可以编写为仅从数据库中获取一个值,然后允许您指定实际如何处理该值,因此它接受一个函数。你不知道何时或是否会回来,但如果/什么时候回来,你知道你想让它做什么。在这里,我传入一个匿名函数(或 lambda),该函数将值发送到 GUI 窗口。通过这样做,我可以很容易地改变程序的行为:grabDBValue

grabDBValue( (lambda x: passToLogger(x) ))

回调在函数为第一类值的语言中效果很好,就像通常的整数、字符串、布尔值等一样。在 C 语言中,你可以通过传递指向函数的指针来“传递”函数,调用方可以使用它;在 Java 中,调用者将请求具有特定方法名称的某种类型的静态类,因为类之外没有函数(实际上是“方法”);在大多数其他动态语言中,你可以用简单的语法传递一个函数。

产品提示:

在具有词法范围的语言(如 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

评论

1赞 TarkaDaal 3/16/2012
+1.我其实很喜欢这个答案。对回调解释简单明了。
22赞 Brian Nickel 3/11/2012 #8

想象一下,你需要一个返回 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)。

除了简化代码之外,它还允许异步代码通过在完成时调用任意函数来通知您它已完成,类似于您打电话给某人并留下回拨号码。

评论

0赞 Quazi Irfan 7/18/2016
因此,您将函数作为参数传递。是否所有编程语言都允许将函数作为参数传递?如果没有,那么您能否举例说明如何在这些语言上实现回调函数。
4赞 tonylo 3/11/2012 #9

一个隐喻的解释:

我有一个包裹想送给朋友,我也想知道我的朋友什么时候收到。

所以我把包裹带到邮局,请他们送。如果我想知道我的朋友何时收到包裹,我有两个选择:

(a) 我可以在邮局等到它送达。

(b) 收到一封电子邮件后,我会收到一封电子邮件。

选项 (b) 类似于回调。

645赞 Josh Imhoff 3/11/2012 #10

我将尽量保持这个死法简单。“回调”是由另一个函数调用的任何函数,该函数将第一个函数作为参数。很多时候,“回调”是在发生某些事情时调用的函数。用程序员的话来说,这个东西可以称为“事件”。

想象一下这样的场景:您期待几天后收到包裹。该包裹是送给邻居的礼物。因此,一旦你拿到包裹,你就希望把它带给邻居。你不在城里,所以你给你的配偶留下指示。

你可以告诉他们拿到包裹并把它带给邻居。如果你的配偶像电脑一样愚蠢,他们会坐在门口等包裹,直到它来(不做任何其他事情),然后一旦它来了,他们就会把它带给邻居。但是有更好的方法。告诉你的配偶,一旦他们收到包裹,他们应该把它带到邻居那里。然后,他们可以正常生活,直到他们收到包裹。

在我们的示例中,接收包是“事件”,将其带给邻居是“回调”。您的配偶“运行”您的指示,仅在包裹到达才将包裹带过来。好多了!

这种想法在日常生活中是显而易见的,但计算机却没有同样的常识。考虑一下程序员通常如何写入文件:

fileObject = open(file)
# now that we have WAITED for the file to open, we can write to it
fileObject.write("We are writing to the file.")
# now we can continue doing the other, totally unrelated things our program does

在这里,我们等待文件打开,然后再写入它。这“阻塞”了执行流程,我们的程序无法执行它可能需要做的任何其他事情!如果我们能这样做呢?

# we pass writeToFile (A CALLBACK FUNCTION!) to the open function
fileObject = open(file, writeToFile)
# execution continues flowing -- we don't wait for the file to be opened
# ONCE the file is opened we write to it, but while we wait WE CAN DO OTHER THINGS!

事实证明,我们用一些语言和框架来做到这一点。这很酷!查看 Node.js 来获得这种思维的真实实践。

评论

8赞 Haralan Dobrev 3/14/2012
这是正确的,但并未涵盖回调的所有常见用例。通常,当您需要调用一个带有参数的函数时,您会使用回调,这些参数将在另一个函数的进程中处理。例如,在 PHP 中,array_filter() 和 array_map() 接受循环中调用的回调。
4赞 Kenneth K. 2/14/2015
写入文件示例是否合适?它似乎对工作方式做出了假设。在等待操作系统施展其黑魔法时,可能会在内部阻塞,并在此基础上执行回调,这是合理的。在这种情况下,结果没有区别。openopen
33赞 Premraj 7/14/2015
很好的解释,但我有点困惑。Callback 是多线程的吗?
2赞 ChristoKiwi 6/21/2016
很好的例子!到处都在寻找简单的英语,这是我迄今为止找到的第一个:)
1赞 Koray Tugay 3/23/2017
是什么让示例中的 open 函数不阻塞?Open 可能仍会阻止执行流程。.
11赞 Hanno Fietz 3/11/2012 #11

有两点需要解释,一是回调的工作原理(传递一个可以在不了解其上下文的情况下调用的函数),另一点是它的用途(异步处理事件)。

等待包裹到达的类比已被其他答案使用,这是一个很好的解释两者的类比。在计算机程序中,你会告诉计算机期待一个包裹。通常,它现在会坐在那里等待(并且不做其他任何事情)直到包裹到达,如果它永远不会到达,可能会无限期地到达。对于人类来说,这听起来很傻,但如果没有进一步的措施,这对计算机来说是完全自然的。

现在回调将是你前门的铃声。您为包裹服务提供了一种通知您包裹到达的方法,而无需他们知道您在房子里的位置(即使)或铃铛是如何工作的。(例如,一些“铃铛”实际上会发出电话呼叫。因为你提供了一个可以随时“调用”的“回调函数”,所以你现在可以停止坐在前廊,随时“处理事件”(包裹到达)。

评论

0赞 Jeb50 11/2/2017
这是通俗易懂的英语。
0赞 Vishal Sharma 4/19/2018
这似乎是最好的解释!
0赞 Elliptical view 7/29/2020
第一段 +1。谢谢。我发现传递函数在处理事件以外的许多其他方法中都很有用。例如,您可能想要一个通用的排序例程,并向其传递实际函数以比较记录。
6赞 Andrew Ducker 3/11/2012 #12

您有一些要运行的代码。通常,当您调用它时,您正在等待它完成,然后再继续(这可能会导致您的应用程序变灰/为光标产生旋转时间)。

另一种方法是并行运行此代码并继续您自己的工作。但是,如果您的原始代码需要根据它调用的代码的响应执行不同的操作,该怎么办?好吧,在这种情况下,您可以传入您希望它在完成后调用的代码的名称/位置。这是“回电”。

普通代码:询问信息->处理信息->处理结果->继续做其他事情。

使用回调:询问信息->处理信息->继续做其他事情。并在稍后的某个时间点>处理处理结果。

23赞 tovmeod 3/11/2012 #13

程序员约翰尼需要一台订书机,所以他去办公室供应部门要一个,填写完申请表后,他可以站在那里等店员去仓库里寻找订书机(比如阻塞函数调用),或者去做其他事情。

由于这通常需要时间,Johny 在申请表上放了一张纸条,要求他们在订书机准备好取货时给他打电话,这样他就可以去做其他事情,比如在桌子上打盹。

评论

1赞 Thomas 2/24/2015
这似乎更像是承诺而不是回调:blog.jcoglan.com/2013/03/30/......
2赞 Deven Phillips 1/25/2017
Promise 只是围绕回调的语法糖。
3赞 Optimist 3/12/2012 #14

回调是计划在满足条件时执行的方法。

一个“现实世界”的例子是当地的视频游戏商店。您正在等待《半条命 3》。与其每天去商店查看它是否在商店中,不如将电子邮件注册到一个列表中,以便在游戏可用时收到通知。电子邮件成为您的“回电”,需要满足的条件是游戏的可用性。

“程序员”示例是一个网页,您希望在单击按钮时执行操作。注册按钮的回调方法,并继续执行其他任务。当用户点击按钮时,浏览器将查看该事件的回调列表并调用您的方法。

回调是一种异步处理事件的方法。你永远无法知道回调何时会被执行,或者它是否会被执行。优点是它可以在等待回复时释放程序和 CPU 周期来执行其他任务。

评论

0赞 Deven Phillips 1/25/2017
说它是“预定的”可能会在这里引起混淆。回调通常用于异步系统,并且不会有“计划”,而是触发回调执行的“事件”。
3赞 Nael El Shawwa 3/12/2012 #15

用简单的英语来说,回调是一个承诺。Joe、Jane、David 和 Samantha 共用一辆拼车上班。乔今天开车。Jane、David 和 Samantha 有几个选择:

  1. 每 5 分钟检查一次窗口,看看 Joe 是否外出
  2. 继续做他们的事,直到乔按响门铃。

选项 1:这更像是一个轮询示例,其中 Jane 将陷入检查 Joe 是否在外面的“循环”中。与此同时,简不能做任何其他事情。

选项 2:这是回调示例。Jane 告诉 Joe 在外面时按门铃。她给了他一个“功能”来按门铃。Joe 不需要知道门铃是如何工作的或它在哪里,他只需要调用该函数,即当他在那里时按门铃。

回调由“事件”驱动。在这个例子中,“事件”是乔的到来。例如,在 Ajax 中,事件可以是异步请求的“成功”或“失败”,并且每个事件可以具有相同或不同的回调。

在 JavaScript 应用程序和回调方面。我们还需要了解“闭包”和应用程序上下文。“this”指的是什么,很容易让 JavaScript 开发人员感到困惑。在这个例子中,在每个人的 “ring_the_door_bell()” 方法/回调中,每个人可能需要根据他们早上的例行公事执行一些其他方法,例如 “turn_off_the_tv()”。我们希望“this”指的是“Jane”对象或“David”对象,这样每个人都可以在 Joe 拿起它们之前设置他们需要完成的任何其他操作。在这里,使用 Joe 设置回调需要模仿该方法,以便“this”引用正确的对象。

希望对您有所帮助!

97赞 user508994 3/12/2012 #16

如何用通俗易懂的英语解释回调?

简单来说,回调函数就像一个 Worker,当他完成一个任务时,他会“回拨”给他的经理

它们与从一个函数调用另一个函数有何不同 从调用函数中获取一些上下文?

确实,您正在从另一个函数调用一个函数,但关键是回调被视为一个对象,因此您可以根据系统的状态(如策略设计模式)更改要调用的函数。

如何向新手程序员解释它们的力量?

在需要从服务器中提取数据的 AJAX 风格的网站中,可以很容易地看到回调的强大功能。下载新数据可能需要一些时间。如果没有回调,整个用户界面将在下载新数据时“冻结”,或者您需要刷新整个页面而不仅仅是其中的一部分。通过回调,您可以插入“正在加载”的图像,并在加载后将其替换为新数据。

一些没有回调的代码:

function grabAndFreeze() {
    showNowLoading(true);
    var jsondata = getData('http://yourserver.com/data/messages.json');
    /* User Interface 'freezes' while getting data */
    processData(jsondata);
    showNowLoading(false);
    do_other_stuff(); // not called until data fully downloaded
}

function processData(jsondata) { // do something with the data
   var count = jsondata.results ? jsondata.results.length : 0;
   $('#counter_messages').text(['Fetched', count, 'new items'].join(' '));
   $('#results_messages').html(jsondata.results || '(no new messages)');
}

使用回调:

下面是一个回调示例,使用 jQuery 的 getJSON

function processDataCB(jsondata) { // callback: update UI with results
   showNowLoading(false);
   var count = jsondata.results ? jsondata.results.length : 0;
   $('#counter_messages').text(['Fetched', count, 'new items'].join(' '));
   $('#results_messages').html(jsondata.results || '(no new messages)');
}

function grabAndGo() { // and don't freeze
    showNowLoading(true);
    $('#results_messages').html(now_loading_image);
    $.getJSON("http://yourserver.com/data/messages.json", processDataCB);
    /* Call processDataCB when data is downloaded, no frozen User Interface! */
    do_other_stuff(); // called immediately
}

带闭合:

通常,回调需要使用 a 从调用函数访问,这就像 Worker 需要从 Manager 获取信息才能完成他的任务一样。要创建 ,您可以内联该函数,以便它在调用上下文中看到数据:stateclosureclosure

/* Grab messages, chat users, etc by changing dtable. Run callback cb when done.*/
function grab(dtable, cb) { 
    if (null == dtable) { dtable = "messages"; }
    var uiElem = "_" + dtable;
    showNowLoading(true, dtable);
    $('#results' + uiElem).html(now_loading_image);
    $.getJSON("http://yourserver.com/user/"+dtable+".json", cb || function (jsondata) {
       // Using a closure: can "see" dtable argument and uiElem variables above.
       var count = jsondata.results ? jsondata.results.length : 0, 
           counterMsg = ['Fetched', count, 'new', dtable].join(' '),
           // no new chatters/messages/etc
           defaultResultsMsg = ['(no new ', dtable, ')'].join(''); 
       showNowLoading(false, dtable);
       $('#counter' + uiElem).text(counterMsg);
       $('#results'+ uiElem).html(jsondata.results || defaultResultsMsg);
    });
    /* User Interface calls cb when data is downloaded */

    do_other_stuff(); // called immediately
}

用法:

// update results_chatters when chatters.json data is downloaded:
grab("chatters"); 
// update results_messages when messages.json data is downloaded
grab("messages"); 
// call myCallback(jsondata) when "history.json" data is loaded:
grab("history", myCallback); 

关闭

最后,这是道格拉斯·克罗克福德(Douglas Crockford)的定义:closure

函数可以在其他函数中定义。内部函数可以访问外部函数的变量和参数。如果对内部函数的引用仍然存在(例如,作为回调函数),则外部函数的 var 也会仍然存在。

另请参阅:

评论

16赞 TarkaDaal 3/16/2012
+1.第一段是砰的钱。然而,其余部分很快就进入了计算机科学术语。
0赞 BigJMoney 5/7/2022
第一个代码片段显示了异步调用的必要性,但我不明白为什么回调是必要的。你可以通过从 grabAndFreeze 中删除第一行和最后一行,并将它们移动到异步调用 grabAndFreeze() 的外部函数中来解决没有回调的问题。我想回调的价值是针对经常在不同上下文中异步调用的代码?为了保存 DRY?我可能有一个程序可以获取一次数据,我确实希望它是异步的,但不需要回调,对吗?
26赞 JoeyG 3/13/2012 #17

在非程序员的术语中,回调是程序中的填空。

许多纸质表格上的一个常见项目是“紧急情况下呼叫的人”。那里有一个空行。你写上某人的姓名和电话号码。如果发生紧急情况,则会呼叫该人。

  • 每个人都得到相同的空白表格,但是
  • 每个人都可以写一个不同的紧急联系电话。

这是关键。您不会更改表单(代码,通常是其他人的代码)。但是,您可以填写缺失的信息(您的号码)。

示例 1:

回调用作自定义方法,可能用于添加/更改程序的行为。例如,以一些执行函数但不知道如何打印输出的 C 代码为例。它所能做的就是做一个字符串。当它试图弄清楚如何处理字符串时,它会看到一个空行。但是,程序员给了你一个空白来写你的回调!

在此示例中,您不使用铅笔在一张纸上填写空白,而是使用函数 。set_print_callback(the_callback)

  • 模块/代码中的空白变量是空白行,
  • set_print_callback是铅笔,
  • 并且是您正在填写的信息。the_callback

您现在已经在程序中填写了这个空白行。每当它需要打印输出时,它都会查看该空行,并按照那里的说明进行操作(即调用您放在那里的函数)。实际上,这允许打印到屏幕、日志文件、打印机、通过网络连接或它们的任意组合。你已经用你想做的事情填空了。

示例 2:

当您被告知需要拨打紧急电话时,您可以阅读纸质表格上的内容,然后拨打您阅读的号码。如果该行为空,则不执行任何操作。

Gui 编程的工作方式大致相同。单击按钮时,程序需要弄清楚下一步该做什么。它去寻找回调。此回调恰好位于标记为“单击 Button1 时执行的操作”的空白中

大多数IDE会在你要求时自动为你填空(编写基本方法)(例如)。但是,该空白可以采用任何方法,请您放心。您可以调用该方法,也可以将该回调的名称放在适当的空白处。您可以将“555-555-1212”放在紧急号码空白处。这没有多大意义,但这是允许的。button1_clickedrun_computationsbutter_the_biscuits


最后说明:您用回调填写的空白行?它可以随意擦除和重写。(你是否应该是另一个问题,但这是他们权力的一部分)

5赞 Gulshan 3/14/2012 #18

对于教学回调,您必须先教授指针。一旦学生理解了指向变量的指针的概念,回调的想法就会变得更容易。假设您使用的是 C/C++,则可以执行以下步骤。

  • 首先,向学生展示如何使用指针以及使用普通变量标识符来使用和操作变量。
  • 然后教他们有些事情只能用指针来完成(比如通过引用传递变量)。
  • 然后告诉他们可执行代码或函数如何像内存中的其他数据(或变量)一样。因此,函数也有地址或指针。
  • 然后向他们展示如何使用函数指针调用函数,并告诉这些函数称为回调。
  • 现在的问题是,为什么调用某些函数会如此麻烦?有什么好处?与数据指针一样,函数指针(又名回调)与使用普通标识符相比具有一些优势。
  • 第一个是,函数标识符或函数名称不能用作普通数据。我的意思是,你不能用函数(如数组或函数链表)制作数据结构。但是通过回调,你可以创建一个数组、一个链表,或者将它们与其他数据一起使用,比如在键值对或树的字典中,或任何其他东西。这是一个强大的好处。而其他好处实际上是这个的孩子。
  • 回调最常见的用法是事件驱动程序编程。其中一个或多个功能基于某些输入信号执行。使用回调,可以维护字典以映射带有回调的信号。然后,输入信号的解析和相应代码的执行变得更加容易。
  • 我脑海中回调的第二个用途是高阶函数。将其他函数作为输入参数的函数。为了将函数作为参数发送,我们需要回调。一个示例可以是接受数组和回调的函数。然后,它对数组的每个项目执行回调,并在另一个数组中返回结果。如果我们向函数传递一个加倍回调,我们会得到一个加倍值数组。如果我们传递一个平方回调,我们就会得到平方。对于平方根,只需发送适当的回调即可。这不能用正常功能来完成。

可能还有更多的事情。让学生参与进来,他们就会发现。希望这会有所帮助。

评论

1赞 Gulshan 3/14/2012
我的另一个答案与 programmers.SE programmers.stackexchange.com/a/75449/963 中的这个话题有关
0赞 Elliptical view 7/29/2020
在你的最后一颗子弹中,虽然术语回调在现代用于此,但我们只是在运行一个带有参数的函数,而该参数是另一个函数。(如果堆栈被关注,它甚至可能是它自己。据我所知,这种结构比事件、回调和 GUI 结构要古老得多。它可以追溯到汇编语言,远远早于我们拥有 GUI 之前。我想我记得在 CPM 中看到过它。
6赞 David Casseres 3/14/2012 #19

回调是将由第二个函数调用的函数。第二个函数事先不知道它将调用什么函数。因此,回调函数的标识存储在某处,或作为参数传递给第二个函数。根据编程语言的不同,这个“标识”可能是回调的地址,或者是某种其他类型的指针,也可能是函数的名称。原理是一样的,我们存储或传递一些明确标识函数的信息。

时机成熟时,第二个函数可以调用回调,根据当时的情况提供参数。它甚至可以从一组可能的回调中选择回调。编程语言必须提供某种语法,以允许第二个函数调用回调,知道其“身份”。

这种机制有很多可能的用途。使用回调,函数的设计者可以通过调用提供的任何回调来自定义函数。例如,排序函数可能将回调作为参数,而此回调可能是用于比较两个元素以决定哪个元素先出现的函数。

顺便说一句,根据编程语言的不同,上述讨论中的“函数”一词可能会被“块”、“闭包”、“lambda”等取代。

7赞 Luciano 3/14/2012 #20

没有回调,也没有其他特殊的编程资源(如线程等),程序恰恰是一系列指令,这些指令一个接一个地按顺序执行,即使具有某种由某些条件决定的“动态行为”,所有可能的场景都应该事先编程

因此,如果我们需要为程序提供真正的动态行为,我们可以使用回调。通过回调,你可以通过参数指示,一个程序调用另一个程序,提供一些先前定义的参数,并可以期待一些结果(这是合约或操作签名),因此这些结果可以由第三方程序生成/处理,这是以前不知道的。

这种技术是应用于程序、函数、对象和计算机运行的所有其他代码统一性的多态性的基础。

当你在做一些工作时,作为回调的例子的人类世界得到了很好的解释,假设你是一个画家(这里你是主程序,画画),有时打电话给你的客户,要求他批准你的工作结果,所以,他决定图片是否好(你的客户是第三方程序)。

在上面的例子中,你是一个画家,将批准结果的工作“委托”给其他人,图片是参数,每个新客户端(回调的“函数”)都会改变你的工作结果,决定他对图片的要求(客户端做出的决定是“回调函数”返回的结果)。

我希望这个解释能有所帮助。

6赞 steamer25 3/14/2012 #21

让我们假设你要给我一个可能长期运行的任务:获取你遇到的前五个独特人物的名字。如果我在人口稀少的地区,这可能需要几天时间。当我跑来跑去时,你对坐在你的手上并不感兴趣,所以你说,“当你拿到名单时,在我的手机上给我打电话,然后读给我听。这是数字。

你给了我一个回调引用——一个我应该执行的函数,以便进行进一步的处理。

在 JavaScript 中,它可能看起来像这样:

var lottoNumbers = [];
var callback = function(theNames) {
  for (var i=0; i<theNames.length; i++) {
    lottoNumbers.push(theNames[i].length);
  }
};

db.executeQuery("SELECT name " +
                "FROM tblEveryOneInTheWholeWorld " +
                "ORDER BY proximity DESC " +
                "LIMIT 5", callback);

while (lottoNumbers.length < 5) {
  playGolf();
}
playLotto(lottoNumbers);

这可能可以通过很多方式进行改进。例如,您可以提供第二个回拨:如果最终花费的时间超过一个小时,请拨打红色电话并告诉接听的人您已超时。

3赞 pete 3/14/2012 #22

回电是回邮的回邮信封。当你调用一个函数时,这就像发送一封信一样。如果希望该函数调用另一个函数,请以引用或地址的形式提供该信息。

2赞 Andrei Vajna II 3/15/2012 #23

简单明了:回调是你给另一个函数的一个函数,以便它可以调用它。

通常在完成某些操作时调用它。由于在将回调提供给其他函数之前创建回调,因此可以使用调用站点中的上下文信息对其进行初始化。这就是为什么它被命名为 call*back* - 第一个函数从调用它的位置回调到上下文中。

4赞 yunzen 3/17/2012 #24

我认为这是一项相当容易解释的任务。

乍一看,回调只是普通函数。
更进一步,我们从另一个函数内部调用这个函数(我们称之为 A)(我们称之为 B)。

它的神奇之处在于,我决定哪个函数应该由函数从 B 外部调用。

在我编写函数 B 时,我不知道应该调用哪个回调函数。 在我调用函数 B 时,我还告诉这个函数调用函数 A。仅此而已。

3赞 Sachin Mhetre 3/26/2012 #25

什么是回调函数?

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

回调函数很难跟踪,但有时非常有用。尤其是在设计库时。回调函数就像要求你的用户给你一个函数名称,你会在特定条件下调用该函数。

例如,您编写一个回调计时器。它允许您指定持续时间和要调用的函数,并且函数将相应地回调。“每 10 秒运行 myfunction(),持续 5 次”

或者你可以创建一个函数目录,传递一个函数名称列表,并要求库相应地回调。“如果成功,则回调 success(),如果失败,则回调 fail()。”

让我们看一个简单的函数指针示例

void cbfunc()
{
     printf("called");
}

 int main ()
 {
                   /* function pointer */ 
      void (*callback)(void); 
                   /* point to your callback function */ 
      callback=(void *)cbfunc; 
                   /* perform callback */
      callback();
      return 0; 
}

如何将参数传递给回调函数?

观察到实现回调的函数指针接受 void *,表示它可以接受任何类型的变量,包括结构体。因此,您可以按结构传入多个参数。

typedef struct myst
{
     int a;
     char b[10];
}myst;

void cbfunc(myst *mt) 
{
     fprintf(stdout,"called %d %s.",mt->a,mt->b); 
}

int main() 
{
       /* func pointer */
    void (*callback)(void *);       //param
     myst m;
     m.a=10;
     strcpy(m.b,"123");       
     callback = (void*)cbfunc;    /* point to callback function */
     callback(&m);                /* perform callback and pass in the param */
     return 0;   
}
8赞 DejanLekic 1/11/2014 #26

回拨最容易用电话系统来描述。函数呼叫类似于打电话给某人,问她一个问题,得到答案,然后挂断电话;添加回调会改变类比,以便在问她一个问题后,你还会给她你的姓名和号码,这样她就可以给你回电并给出答案。——保罗·雅库比克 、“C++ 中的回调实现”

评论

0赞 6/7/2018
一个更简单的解释是:我打电话给某人,她正在开会,我留下一个电话号码,她回电话。
52赞 Robert Polevoi 10/9/2015 #27

我惊讶地看到这么多聪明人没有强调“回调”这个词已经以两种不一致的方式使用的现实。

这两种方式都涉及通过将附加功能(函数定义,匿名或命名)传递给现有函数来自定义函数。即。

customizableFunc(customFunctionality)

如果自定义功能只是插入到代码块中,则表示您已经自定义了该函数,如下所示。

    customizableFucn(customFunctionality) {
      var data = doSomthing();
      customFunctionality(data);
      ...
    }

尽管这种注入的功能通常被称为“回调”,但它没有任何偶然性。一个非常明显的例子是 forEach 方法,其中自定义函数作为参数提供,应用于数组中的每个元素以修改数组。

但这与使用“回调”函数进行异步编程(如在 AJAX 或 node.js 中)或简单地将功能分配给用户交互事件(如鼠标单击))有根本区别。在这种情况下,整个想法是在执行自定义功能之前等待或有事件发生。这在用户交互的情况下是显而易见的,但在可能需要时间的 I/O(输入/输出)过程中也很重要,例如从磁盘读取文件。这就是术语“回调”最明显的地方。一旦启动了 I/O 进程(例如要求从磁盘读取文件或服务器以从 http 请求返回数据),异步程序就不会等待它完成。它可以继续执行接下来计划的任何任务,并且仅在收到读取文件或 http 请求已完成(或失败)并且数据可用于自定义功能的通知后才使用自定义功能进行响应。这就像打电话给企业并留下您的“回拨”号码,这样他们就可以在有人回复您时给您打电话。这总比在谁知道要花多久而无法处理其他事务上挂线要好。

异步使用本质上涉及一些侦听所需事件的方法(例如,I/O 过程的完成),以便在它发生时(并且仅在它发生时)执行自定义“回调”功能。在显而易见的 AJAX 示例中,当数据实际从服务器到达时,会触发“回调”函数以使用该数据来修改 DOM,从而将浏览器窗口重绘到该程度。

回顾一下。有些人使用“回调”一词来指代可以作为参数注入现有函数的任何类型的自定义功能。但是,至少对我而言,这个词最合适的用法是异步使用注入的“回调”函数——仅在它等待通知的事件发生时执行。

评论

1赞 MagicLAMP 10/10/2015
那么当函数回调时,进程返回到哪里呢?例如,如果有四行代码;1.fileObject = open(file, writeToFile);2. 做某事1();3. 做某事2();4. doSomething3()。
0赞 MagicLAMP 10/10/2015
执行第 1 行,但不是等待文件打开,而是继续执行第 2 行,然后是第 3 行。此时,文件打开并(一旦第 3 行完成任何信号量操作)回调到程序计数器,说“将控制权传递给 writeToFile”,它会执行操作,完成后,将控制权传递回第 3 行中发生 INT 的点,或者如果第 3 行完成,则传递到第 4 行。
1赞 mikermcneil 10/23/2015
这是对另一个重要观点的非常清晰的解释:例如,作为 arg 传入的函数和作为 arg 传入的函数之间的区别,就您对程序的推理方式而言,它们是不同颜色的马。Array.prototype.forEach()setTimeout()
6赞 somenath mukhopadhyay 5/16/2017 #28

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

在 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.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)();                               
}

如果我们运行这个程序,输出将是

这是一个演示函数回调的程序 内部register_callback 内部my_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;
}
}

活动类

软件包 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,被叫方只能通知一个调用方。但是,对于 EventListener 模式,应用程序的任何其他部分或类都可以注册 Button 或 Checkbox 上可能发生的事件。此类的示例是 OtherClass。如果看到 OtherClass 的代码,就会发现它已将自身注册为 ClickEvent 的侦听器,该侦听器可能发生在 Activity 中定义的 Button 中。有趣的是,除了 Activity(调用方)之外,每当 Button 上发生单击事件时,也会通知此 OtherClass。

7赞 Adam Zerner 4/6/2021 #29

现实生活中的例子

这是我自己生活中的一个真实例子。

当我今天下午 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 无礼。

3赞 Ed Shelton 9/17/2022 #30

A 是一个函数,作为参数传递到另一个函数中(& 在某个时候使用)。callback function

以下是一些功能:

def greeting(name):
    print("Hello " + name + "!")

def departing(name):
    print("Goodbye " + name + "!")

下面是一个函数(用作回调参数):ourCallBack

def promptForName(ourCallback):
    myName = input("Enter Name:")
    ourCallback(myName)

现在让我们使用一些回调!

promptForName(greeting) 
# Enter Name: 
# >Ed
# Hello Ed!

promptForName(departing) 
# Enter Name: 
# >Ed
# Goodbye Ed!

promptForName(greeting) 
# Enter Name: 
# >Guy
# Hello Guy!

我能够非常快速地扩展我的代码。


解决(错误和误导)答案:

回调并不意味着异步!

JS 在 ~2015 年得到了承诺,在 ~2017 年得到了 async/await。在此之前,使用了回调。

这就是为什么这里的一些答案没有意义,它们将两者混为一谈!

它们通常用于异步代码,但我的示例是同步的。

回调并不意味着事件驱动!

它们通常用于事件处理,但我的示例不是事件。

回调并不意味着关闭!

虽然经常被用作提供闭包的巧妙方式,但我的例子却没有。

回调不是第一类函数的完整定义!

它是创建第一类函数定义的众多功能之一。

C 可以使用函数指针作为回调,尽管没有第一类函数。