如何用普通英语解释回调?它们与从调用函数获取某些上下文的另一函数调用另一个函数有何不同?如何向新手程序员解释其功能?

评论

我相信它的学名是延续超越风格。您可以在Wiki上搜索。

关于Quora的相同问题也有一些很好的答案

相关问题:stackoverflow.com/questions/824234/what-is-a-callback-function

我曾经在youtube.com/watch?v=xHneyv38Jro
找到的关于回调的最佳解释

#1 楼

通常,应用程序需要根据其上下文/状态执行不同的功能。为此,我们使用一个变量来存储有关要调用的函数的信息。根据其需要,应用程序将使用要调用的函数的信息来设置此变量,并使用相同的变量来调用该函数。

在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]


评论


虽然从技术上讲这是一个回调,但给出的解释与通用函数指针似乎并不清楚。包括一些为什么可以使用回调的理由。

–埃里克
2014年10月10日17:36

我不明白这个答案。可以讲的不仅仅是代码吗?

– Abhishek Singh
15年10月16日在9:13

为什么我们不能做我们在processArray(arr,callback)函数中的回调函数(function(arg))中所做的事情

–阿比
16年6月25日在9:19

@JoSmo您部分正确。将变量+回调函数作为参数传递,并使用原始函数使用该变量创建的结果,并将其传递给回调函数以进行进一步处理。例如:func1(a,callback_func){v = a + 1},并且有预定义的回调函数:callback_func(v){return v + 1;}这将使a增加2,因此如果在其中传递整数4的参数参数“ a”,callback_funct将返回6作为结果。阅读我找到的此callbackhell.com最佳资源。

–粪
17年11月1日在10:03



这个答案不清楚。可以更简单明了!

– Arjun Kalidas
19年5月5日在13:16

#2 楼

我将尝试使这一死角变得简单。 “回调”是由将第一个功能作为参数的另一个功能调用的任何功能。很多时候,“回调”是在发生某些情况时调用的函数。可以将这种情况称为程序员所说的“事件”。包装是给邻居的礼物。因此,一旦获得包裹,就希望将其带给邻居。您不在城里,所以给配偶留下了指示。

您可以告诉他们拿包并带给邻居。如果您的配偶像计算机一样愚蠢,他们会坐在门口等待包裹,直到它来了(别无所求),然后一旦到达,他们就会把它带给邻居。但是有更好的方法。告诉您的配偶,他们一旦收到包裹,就应将包裹带给邻居。然后,他们可以正常生活,直到收到包裹为止。

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

这种思维在日常生活中很明显,但是计算机却没有相同的常识。考虑程序员通常如何写入文件:

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


在这里,我们在写入文件之前等待文件打开。这“阻塞”了执行流程,我们的程序无法执行它可能需要执行的其他任何操作!如果我们可以改为执行以下操作,该怎么办:它太酷了!查看Node.js,以这种思维获得一些实际的实践。

评论


这是正确的,但并未涵盖回调的所有常见用例。当您需要使用带有参数的函数来调用函数时,通常会使用回调,这些参数将在另一个函数的过程中进行处理。例如,在PHP中,array_filter()和array_map()接受在循环中调用的回调。

– Haralan Dobrev
2012年3月13日23:33



写文件示例合适吗?似乎对开放的运作方式做出了假设。在等待操作系统执行其不可思议的操作时,打开可能会在内部阻塞,然后执行回调。在这种情况下,结果没有差异。

–肯尼斯·K。
15年2月13日在21:12

很好的解释,但我有点困惑。回调是多线程的吗?

– Premraj
15年7月14日在5:47



很好的例子!正在到处寻找简单的英语,这是我到目前为止发现的第一个:)

–克里斯托·基维(ChristoKiwi)
16年6月21日在4:36

是什么使示例中的open函数不阻塞?打开可能仍然会阻止执行流程。

–科雷·图吉(Koray Tugay)
17 Mar 22 '17 at 18:55

#3 楼


如何用普通英语解释回调?


用简单的英语来说,回调函数就像一个Worker,在完成任务后会“回调”给Manager。


它们与从另一个函数中调用一个函数有什么不同
从调用函数中获取上下文吗?您正在从另一个函数调用一个函数,但是关键是回调被视为对象,因此您可以根据系统状态(例如“策略设计模式”)来更改要调用的函数。


如何向新手程序员解释其功能?


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

一些没有回调的代码: />
使用回调:

以下是使用jQuery的getJSON进行回调的示例:

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)');
}


使用闭包:

通常,回调需要使用state从调用函数访问closure,这就像Worker在完成任务之前需要先从Manager获取信息。要创建closure,可以内联函数,以便它在调用上下文中查看数据:

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
}


用法:

/* 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
}


关闭

最后,这是道格拉斯·克罗克福德(Douglas Crockford)对closure的定义:可以在其他函数内部定义函数。内部函数可以访问外部函数的var和参数。如果保留对内部函数的引用(例如,作为回调函数),则外部函数的var也将保留。

另请参见:


http: //javascript.crockford.com/survey.html
http://api.jquery.com/jQuery.when/
http://api.jquery.com/jQuery.getJSON/
http://github.com/josher19/jQuery-Parse


评论


+1。第一段是钱的爆炸。但是,其余部分很快就进入了计算机科学术语。

– TarkaDaal
2012年3月16日上午10:14

#4 楼

令我惊讶的是,有这么多有才华的人未能强调现实,即“回调”一词已经以两种不一致的方式使用。功能(现有功能的功能定义,匿名或命名)。即。

customizableFunc(customFunctionality)


如果将自定义功能简单地插入到代码块中,则您已经自定义了该功能,就像这样。 >
尽管这种注入的功能通常称为“回调”,但并没有任何条件。一个非常明显的例子是forEach方法,其中提供了一个自定义函数作为自变量,以将其应用于数组中的每个元素以修改数组。

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

异步使用本质上涉及一些监听所需事件的方法(例如,完成i / o进程),以便在发生(且仅在发生时)执行自定义的“回调”功能。在一个明显的AJAX示例中,当数据实际从服务器到达时,将触发“回调”功能以使用该数据来修改DOM,从而在此程度上重绘浏览器窗口。回顾一下。有些人使用“回调”一词来指代任何可以作为参数注入到现有功能中的自定义功能。但是,至少对我而言,最合适的用法是异步使用注入的“回调”功能-仅在等待通知的事件发生时执行。

评论


因此,当函数回调时,过程返回到哪里?例如,如果有四行代码; 1.fileObject = open(文件,writeToFile); 2. doSomething1(); 3. doSomething2(); 4. doSomething3()。

– MagicLAMP
2015年10月10日在8:16

执行了第1行,但不是等待文件打开,而是继续第2行,然后是3。这时文件打开,并且(一旦第3行完成了所有信号量操作)回调到程序计数器,说“将控制权传递给执行该操作的writeToFile”,并在完成时将控制权返回到INT在第3行出现的点,或者如果第3行结束则传递到第4行。

– MagicLAMP
2015年10月10日在8:23

这是对另一个重要点的明确阐述:作为arg传入Array.prototype.forEach()的函数与作为arg传入setTimeout()的函数之间的区别,就您对程序的推理方式而言,它们是不同颜色的。

– mikermcneil
2015年10月23日在8:55



#5 楼

用非程序员的术语来说,回调是程序中的空白。那里有一个空白行。您输入某人的姓名和电话号码。如果发生紧急情况,则将呼叫此人。


每个人都具有相同的空白表格,但是
每个人都可以输入不同的紧急联系电话。

这是关键。您无需更改表单(代码,通常是其他人的表单)。但是,您可以填写缺少的信息(您的电话号码)。

示例1:

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

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

模块/代码中的空白变量是空白行,

set_print_callback是铅笔,
the_callback是您要填写的信息。

现在,您已在程序中填写此空白行。每当需要打印输出时,它都会查看该空白行,并按照此处的说明进行操作(即调用您放置在此处的函数。)实际上,这允许将其打印到屏幕,日志文件,打印机,通过网络连接或其任何组合。您已将要填写的内容填空。

示例2:

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

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

当您要求(例如, button1_clicked)。但是,该空白可​​以有您擅长的任何方法。您可以调用方法run_computationsbutter_the_biscuits,只要将该回调的名称放在适当的空白中即可。您可以在紧急电话号码空白处输入“ 555-555-1212”。没什么意义,但是可以。


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

#6 楼

总是最好从一个例子开始:)。

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

您希望模块B中发生某些事件/情况时通知模块A。但是,模块B不知道关于模块A的信息。它所知道的只是通过模块A提供给它的功能指针指向(模块A的)某个特定功能的地址。当使用功能指针发生特定事件/条件时,将“回调”到模块A中。 A可以在回调函数中做进一步的处理。

*)这里的一个明显好处是,您正在从模块B中提取有关模块A的所有内容。模块B不必关心谁/什么模块A。是。

评论


那么,模块B中提供了A中函数的参数,对吗?

–U.Savas
4月21日12:08

#7 楼

想象一下,您需要一个返回10平方的函数,因此您编写了一个函数:

function tenSquared() {return 10*10;}


后来您需要9平方,因此您编写了另一个函数: >
function nineSquared() {return 9*9;}


最终,您将所有这些替换为通用函数:您有一个执行某项功能的函数,完成后会调用doA: />
function square(x) {return x*x;}


或者您可以将回调函数作为变量传递,而只需具有一次函数:

function computeA(){
    ...
    doA(result);
}


然后,您只需要调用compute(doA)和compute(doB)。

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

评论


因此,您将函数作为参数传递。所有编程语言都允许将函数作为参数传递吗?如果否,那么您可以举例说明如何在这些语言上实现回调函数。

– Quazi Irfan
16年7月17日在23:54

#8 楼

约翰尼(Johny)程序员需要一个订书机,于是他下到办公室供应部门并要求一个订书机。填写请求表后,他可以站在那儿等待文员到仓库四处寻找订书机(例如阻塞功能调用)或同时进行其他操作。

因为这通常需要时间,所以约翰尼将便条纸和请求表放在一起,要求他们在订书机准备好取书时打电话给他,因此他可以去诸如在他的办公桌上小睡。

评论


这似乎更像是承诺而不是回调:blog.jcoglan.com/2013/03/30/…

–托马斯
2015年2月23日在21:19



承诺只是围绕回调的语法糖。

–迪文·菲利普斯(Deven Phillips)
17年1月25日在4:51

#9 楼

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

评论


很好的类比。您能否进一步扩展一下,也许提供一些(简单的)编程相关示例?

–a20
2014年6月4日在5:14

#10 楼

有两点需要说明,一是回调的工作方式(绕过一个可以在不了解其上下文的情况下调用的函数),二是它的用途(异步处理事件)。

比方说,等待包裹被其他答案使用的类比是一个很好的解释。在计算机程序中,您将告诉计算机要包裹。通常,它现在会坐在那里,等待(什么也不做)直到包裹到达,如果它永远不会到达,可能会无限期地到达。对人类来说,这听起来很愚蠢,但是如果不采取进一步措施,这对于计算机来说是完全自然的。您为包裹服务提供了一种通知包裹到达的方式,而无需他们知道您在房子里的什么地方,或者门铃如何工作。 (例如,一些“铃铛”实际上是在拨打电话。)由于您提供了可以随时调用的“回调功能”,因此在上下文无关的情况下,您现在可以停止坐在前廊并“处理电话”。事件”(包裹到达)。

评论


这是简单的英语。

–Jeb50
17年11月2日在4:57

这似乎是最好的解释!

– Vishal Sharma
18年4月19日在6:32

+1为第一段。谢谢。我发现传递函数除了处理事件外,还有许多其他有用的方法。例如,您可能需要通用排序例程,然后将实际函数传递给它以比较记录。

–椭圆视图
7月29日4:19

#11 楼

想象一个朋友要离开你的房子,你告诉她“回家后给我打电话,以使我知道你已经安全抵达了”。 (字面上)是回叫。这就是回调函数,与语言无关。您希望某个过程在完成某些任务后将控制权交还给您,因此您可以给它提供一个函数以供您回调。例如,在Python中,可以将grabDBValue编写为仅从数据库中获取值,然后让您指定对该值实际执行的操作,因此它接受一个函数。您不知道grabDBValue何时或是否返回,但是如果/何时返回,您就知道要执行什么操作。在这里,我传入了一个匿名函数(或lambda),该函数将值发送到GUI窗口。我可以通过执行以下操作轻松更改程序的行为: ,布尔值等。在C中,您可以通过传递指向该函数的指针来“传递”一个函数,调用者可以使用该函数;在Java中,调用者会要求使用某种方法名称的某种类型的静态类,因为类之外没有函数(实际上是“方法”);在大多数其他动态语言中,您只需传递具有简单语法的函数即可。像这样:

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


在这种情况下,$val将是6,因为回调可以访问在定义它的词法环境中声明的变量。词汇范围和匿名回调是一个强大的组合,需要新手程序员进一步研究。

评论


+1。我实际上很喜欢这个答案。关于什么是回调的说明很简洁。

– TarkaDaal
2012年3月16日上午10:08

#12 楼

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

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

普通代码:询问信息->流程信息->处理结果的交易->继续做其他事情。回调:询问信息->流程信息->继续执行其他操作。并在稍后的地方->处理结果的处理。

#13 楼

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

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

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

顺便说一下,具体取决于编程语言,在上面的讨论中,“功能”一词可能由“块”,“闭包”,“ lambda”等代替。

#14 楼

在没有回调的情况下,没有其他特殊的编程资源(例如线程和其他资源),一个程序就是一系列指令,这些指令一个接一个地依次执行,甚至具有由某些条件确定的“动态行为”,所有可能的情况

因此,如果我们需要为程序提供真实的动态行为,则可以使用回调。使用回调,您可以按参数指示,一个程序可以调用另一个程序,该程序提供一些先前定义的参数,并且可以预期一些结果(这是合同或操作签名),因此这些结果可以由第三方程序产生/处理

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

回调的示例很好地解释了您在执行某项工作时的情况,假设您是画家(这里是您要绘画的主程序),有时会打电话给客户,请他批准您的工作结果,因此,如果图片质量很好(您的客户端是第三方程序)。

在上面的示例中,您是画家,并且将工作“委托”他人批准结果,图片是参数,并且每个新客户端(称为“函数”)都会更改您的搜索结果rk决定他想要的图片(客户的决定是“回调函数”的返回结果)。

我希望这种解释会有用。

#15 楼

假设您要给我一个可能会长期运行的任务:获取遇到的前五个独特人员的姓名。如果我在人烟稀少的地区,则可能需要几天的时间。当我四处奔跑时,您真的没有兴趣坐在您的手上,所以您说:“找到清单后,在手机上给我打电话,然后读给我听。 />
您给了我一个回调引用,我应该执行该函数以便进行进一步的处理。

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);


这可能会在很多方面得到改进。例如,您可以提供第二个回叫:如果结束时耗时超过一个小时,请拨打红色电话,并告诉对方您超时的答案。

#16 楼

回叫最容易用电话系统来描述。函数调用类似于打电话给某人,问一个问题,得到答案并挂断电话。添加回叫会改变类比,以便在问了她一个问题之后,还给了她您的姓名和电话号码,以便她可以用答案给您回电。 -Paul Jakubik
,“ C ++中的回调实现”

评论


一个更简单的解释是:我给某人打电话,她正在开会,我留下电话号码,她回电话。

–寂寞
18年6月6日在20:09

#17 楼

对于示教回调,您必须首先示教指针。一旦学生理解了指向变量的指针的想法,回调的想法就会变得更加容易。假设您使用的是C / C ++,则可以按照以下步骤进行操作。告诉他们有些事情只能用指针来完成(例如通过引用传递变量)。然后告诉他们可执行代码或函数如何与内存中的其他数据(或变量)一样。因此,函数还具有地址或指针。
然后向他们展示如何使用函数指针来调用函数,并告诉它们称为回调。有什么好处?像数据指针一样,函数指针又称回调比使用普通标识符具有一些优势。
第一个是,函数标识符或函数名称不能用作普通数据。我的意思是,您不能使用函数(例如数组或函数的链接列表)创建数据结构。但是,有了回调,您可以创建一个数组,一个链表或将它们与其他数据一起使用,例如键值对或树的字典或其他任何东西。这是一个强大的好处。实际上,还有其他好处。
在事件驱动程序编程中可以看到回调的最常见用法。根据某个输入信号执行一个或多个功能的地方。使用回调,可以维护字典以映射带有回调的信号。这样输入信号的分辨率和相应代码的执行就变得更加容易。
我想到的回调的第二种用法是高阶函数。以其他函数为输入参数的函数。为了将函数作为参数发送,我们需要回调。一个示例可以是带有数组和回调的函数。然后,它对数组的每个项目执行回调,并将结果返回到另一个数组中。如果我们向函数传递加倍回调,则将获得双倍值数组。如果我们传递平方回调,则会得到平方。对于平方根,只需发送适当的回调。普通功能无法做到这一点。

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

评论


我的另一个与程序员相关的主题答案。SE程序员.stackexchange.com / a / 75449/963

–古尔山
2012年3月13日18:34

在上一个项目符号中,虽然现代使用了术语“回调”,但我们只是在运行带有参数的函数,而该参数是另一个函数。 (如果有人关注堆栈,甚至可能是它本身。)据我所知,此构造比事件,回调和GUI构造要古老得多。它的历史可以追溯到汇编语言,早于我们拥有GUI的时间。我想我记得在CPM中看到过它。

–椭圆视图
7月29日4:32



#18 楼

通常我们将变量发送给函数。
假设您有任务需要在将变量作为参数赋值之前进行处理-您可以使用回调。如果要处理function1(var1, var2),然后将其作为参数发送?

评论


什么是回调的另一种类型?

–约翰尼
2014年4月20日在20:57

@johnny:完成Ajax等时会触发正常的浏览器回调。

– Nishant
15年1月24日在15:12

#19 楼

隐喻性解释:

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

所以我把包裹拿到了邮局,请他们将其交付。如果我想知道朋友何时收到包裹,我有两种选择:

(a)我可以在邮局等到发货为止。

(b )发送时会收到一封电子邮件。

选项(b)与回调类似。

#20 楼

用简单的英语来说,回调是一个承诺。乔,简,大卫和萨曼莎共用一辆车上班。乔今天开车。简,大卫和萨曼莎有两种选择:


每5分钟检查一次窗户,看看乔是否不在
继续做事,直到乔敲响门铃。

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

方法2:这是回调示例。简告诉乔在外面时给她敲门铃。她给他“门铃”的“功能”。 Joe不需要知道门铃的工作原理或位置,只需要调用该功能即可,即在他在那里的时候就按门铃。

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

关于JavaScript应用程序和回调。我们还需要了解“闭包”和应用程序上下文。 “ this”所指的内容很容易使JavaScript开发人员感到困惑。在此示例中,在每个人的“ ring_the_door_bell()”方法/回调中,每个人可能需要根据自己的早晨例行事例执行其他一些方法。 “ turn_off_the_tv()”。我们希望“ this”引用“ Jane”对象或“ David”对象,以便每个人都可以设置在Joe拿起它们之前需要做的其他事情。在此处使用Joe设置回调需要模仿该方法,以便“ this”指向正确的对象。

希望有帮助!

#21 楼

回调是一个自寻址的盖章信封。调用函数时,就像发送字母一样。如果您希望该函数调用另一个函数,则可以以引用或地址的形式提供该信息。

#22 楼

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

最初的回调只是普通函数。
此外,我们从另一个函数内部调用此函数(我们称之为A) (我们称它为B)。

这样做的神奇之处在于,我决定应该由外部B调用该函数。应该调用回调函数。
当我调用函数B时,我也告诉该函数调用函数A。仅此而已。

#23 楼

什么是回调函数?

第一个问题的简单答案是,回调函数是通过函数指针调用的函数。如果将一个函数的指针(地址)作为参数传递给另一个函数,则当该指针用于调用该函数时,它指向该函数,即表示已进行回调。很难追踪,但有时它非常有用。特别是在设计库时。回调函数就像要求用户为您提供一个函数名称,您将在特定条件下调用该函数。例如,您编写一个回调计时器。它允许您指定持续时间和要调用的函数,并且该函数将相应地回调。 “每10秒运行一次myfunction(),共5次”

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

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

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;   
}


#24 楼

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

“真实世界”示例是本地视频游戏商店。您正在等待《半条命3》,而无需每天去商店看看它是否在里面,而是在列表上注册您的电子邮件,以便在游戏可用时得到通知。电子邮件将成为您的“回调”,而满足的条件是游戏的可用性。

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

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

评论


说这是“预定的”可能会引起混乱。回调通常用于异步系统中,不会有“计划”,而是会触发触发执行的“事件”。

–迪文·菲利普斯(Deven Phillips)
17年1月25日在4:50

#25 楼

简单明了:回调是您提供给另一个函数的一个函数,以便可以调用它。由于您在将回调提供给其他功能之前创建了回调,因此可以使用来自调用站点的上下文信息对其进行初始化。这就是为什么它被命名为call * back *的原因-第一个函数从被调用的位置回调回上下文。

#26 楼

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

在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语言中的回调的概念。 .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个观察者/侦听器特定任务已完成


列表项

回调机制与EventListener /之间的区别观察者机制是在回调中,被调用者通知单个调用者,而在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;
}
}


>活动类

com.som_itsolutions.training.java.exampleeventlistener软件包;

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;
}
}

其他类

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


从上面的代码中可以看出,我们有一个称为事件的接口,该接口基本上列出了应用程序可能发生的所有事件。 Widget类是所有UI组件(如Button,Checkbox)的基类。这些UI组件是实际从框架代码接收事件的对象。窗口小部件类实现事件接口,并且它具有两个嵌套接口,即OnClickEventListener和OnLongClickEventListener

这两个接口负责侦听在Widget派生的UI组件(例如Button或Checkbox)上可能发生的事件。因此,如果将本示例与使用Java接口的早期Callback示例进行比较,则这两个接口将用作Callback接口。因此,较高级别的代码(此处的活动)实现了这两个接口。每当小部件发生事件时,都会调用更高级别的代码(或在更高级别的代码中实现的这些接口的方法,在此处为Activity)。

现在让我讨论回调和事件监听模式之间的区别。正如我们已经提到的,使用回叫,被叫方只能通知一个呼叫者。但是,对于EventListener模式,应用程序的任何其他部分或类都可以注册按钮或复选框上可能发生的事件。此类的示例是OtherClass。如果您看到OtherClass的代码,您会发现它已将自己注册为ClickEvent的侦听器,该事件可能在Activity中定义的Button中发生。有趣的是,除了活动(调用方)之外,只要在Button上发生click事件,此OtherClass也会被通知。

#27 楼

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

更少的硬编码=易于维护和更改=更少的时间=更多的商业价值=令人敬畏。

例如,在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

#28 楼

[编辑]当我们有两个函数,即functionA和functionB时,如果functionA依赖于functionB。

,我们将functionB称为回调函数。这在Spring框架中得到了广泛使用。 />

#29 楼

将方法视为将任务交给同事的方法。一个简单的任务可能如下:

Solve these equations:
x + 2 = y
2 * x = 3 * y


您的同事努力地进行数学运算并给出以下结果: >
但是您的同事有问题,他并不总是理解诸如^之类的符号,但是他确实通过它们的描述来理解它们。如exponent。每当他发现其中之一时,您就会得到以下信息:他并不总是记得介于两个问题之间。而且他也很难记住您的提示,例如问我。他会尽其所能始终遵循您的书面指示。

您想到了一个解决方案,您只需在所有说明中添加以下内容:
现在,每当他遇到问题时,他都会打电话给您并询问,而不是给您一个不好的答复并使流程重新开始。

#30 楼

就下载网页而言,此操作如下:

您的程序在手机上运行,​​并且正在请求网页http://www.google.com。如果您同步编写程序,则为下载数据而编写的功能将一直运行,直到下载所有数据为止。这意味着您的用户界面不会刷新,并且基本上会冻结。如果您使用回调编写程序,则会请求数据并说“完成后执行此函数”。这允许UI在文件下载期间仍然允许用户交互。网页下载完成后,将调用结果函数(回调),您可以处理数据。

基本上,它使您可以请求某些内容并在等待结果时继续执行。一旦通过回调函数将结果返回给您,您就可以在中断处进行操作。