如何用简单的英语解释回调?它们与从另一个函数调用一个函数有何不同?

Posted

技术标签:

【中文标题】如何用简单的英语解释回调?它们与从另一个函数调用一个函数有何不同?【英文标题】:How to explain callbacks in plain english? How are they different from calling one function from another function? 【发布时间】:2012-03-24 16:10:15 【问题描述】:

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

【问题讨论】:

我相信它的学名是延续传递风格。你可以在 wiki 上搜索。 identical question on Quora 也有一些很好的答案 相关问题:***.com/questions/824234/what-is-a-callback-function 我找到的关于回调的最好解释youtube.com/watch?v=xHneyv38Jro 【参考方案1】:

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

想象一下这种情况:您期待几天后收到包裹。该包裹是送给邻居的礼物。因此,一旦您收到包裹,您就希望将其带到邻居那里。你不在城里,所以你给你的配偶留下了指示。

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

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

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

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 以实际练习这种思维方式。

【讨论】:

这是正确的,但并未涵盖回调的所有常见用例。当您需要调用带有参数的函数时,通常会使用回调,该参数将在另一个函数的过程中处理。例如,在 php 中,array_filter() 和 array_map() 需要在循环中调用回调。 写文件的例子合适吗?它似乎对open 的工作方式做出了假设。 open 在等待操作系统执行其黑魔法时可能会在内部阻塞,这似乎是合理的,在此之后执行回调。在这种情况下,结果没有区别。 很好的解释,但我有点困惑。 回调是多线程的吗? 很好的例子!到处都在寻找简单的英语,这是我迄今为止找到的第一个:) 是什么使您的示例中的 open 函数非阻塞? open 可能仍然会阻止执行流程..【参考方案2】:

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

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]

【讨论】:

虽然这在技术上是一个回调,但给出的解释听起来与一般的函数指针没有区别。包含一些为什么可能使用回调的理由会有所帮助。 我不明白这个答案。能比代码多解释吗? 为什么我们不能在processArray(arr,callback)函数中做我们在回调函数(function(arg))中所做的事情 @JoSmo 你是部分正确的。将变量 + 回调函数作为参数传递,将原始函数使用变量创建的结果传递给回调函数以进行进一步处理。例如:func1(a, callback_func) v = a + 1 并且有预定义的回调函数:callback_func(v)return v+1; 这将使 a 增加 2,所以如果你传入整数 4 的参数"a" 参数,callback_funct 将返回 6 作为结果。阅读此callbackhell.com 我找到的最佳来源。 这个答案不清楚。可以更简单、更清晰!【参考方案3】:

如何用简单的英语解释回调?

简单来说,回调函数就像一个Worker,当他完成一个Task时,他会“回调”到他的Manager

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

确实你是从另一个函数调用一个函数,但关键是回调被当作一个对象,所以你可以根据系统的状态来改变调用哪个函数(就像策略设计模式)。

如何向新手程序员解释它们的强大功能?

在需要从服务器提取数据的 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

带闭包:

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

/* 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 也会存在。

另见:

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。第一段是物超所值。但是,其余部分很快就会进入计算机科学术语。【参考方案4】:

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

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

customizableFunc(customFunctionality)

如果自定义功能只是简单地插入到代码块中,那么您已经自定义了该功能,就像这样。

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

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

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

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

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

【讨论】:

那么当函数回调时,进程返回到哪里呢?例如,如果有四行代码; 1.fileObject = open(file, writeToFile); 2.doSomething1(); 3.doSomething2(); 4.doSomething3(). 第 1 行被执行,但不是等待文件被打开,而是进入第 2 行,然后是第 3 行。此时文件打开并且(一旦第 3 行完成任何信号量操作)回调程序计数器,说“将控制权传递给 writeToFile”,它完成后将控制权传递回第 3 行中发生 INT 的点,或者如果第 3 行必须完成,则传递到第 4 行。 这是对另一个重要点的非常清晰的解释:例如作为 arg 传入 Array.prototype.forEach() 的函数与作为 arg 传入 setTimeout() 的函数之间的区别,就你对程序的推理方式而言,它们是不同颜色的马。【参考方案5】:

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

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

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

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

示例 1:

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

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

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

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

示例 2:

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

GUI 编程的工作方式大致相同。当一个按钮被点击时,程序需要弄清楚接下来要做什么。它去寻找回调。此回调恰好位于标有“单击 Button1 时执行的操作”的空白处

大多数 IDE 在您要求时会自动为您填写空白(编写基本方法)(例如button1_clicked)。但是,该空白可​​以有任何你认为不错的方法。你可以调用方法run_computationsbutter_the_biscuits,只要你把回调的名字放在适当的空白处。您可以在紧急号码空白处填写“555-555-1212”。这没有多大意义,但这是允许的。


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

【讨论】:

【参考方案6】:

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

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

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

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

*) 这里的一个明显优势是您可以从模块 B 中抽象出关于模块 A 的所有内容。模块 B 不必关心模块 A 是谁/什么。

【讨论】:

那么,A中函数的参数是在B模块中提供的,对吗?【参考方案7】:

假设您需要一个返回 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)。

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

【讨论】:

所以您将函数作为参数传递。是否所有编程语言都允许将函数作为参数传递?如果不是,那么您能否举例说明如何在这些语言上实现回调函数。【参考方案8】:

程序员 Johny 需要一个订书机,所以他到办公用品部门索要一个,填写申请表后,他可以站在那里等待店员去仓库四处寻找订书机(就像一个阻塞函数调用)或同时去做其他事情。

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

【讨论】:

这似乎更像是承诺而不是回调:blog.jcoglan.com/2013/03/30/… Promise 只是回调的语法糖。【参考方案9】:

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

【讨论】:

很好的比喻。您能否进一步扩展一些(简单的)编程相关示例?【参考方案10】:

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

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

现在回调将是您前门的铃声。您为包裹服务提供了一种通知您包裹到达的方式,而他们不必知道(即使)您在家中的哪个位置,或者铃声是如何工作的。 (例如,一些“铃铛”实际上会发送一个电话。)因为您提供了一个可以随时“调用”的“回调函数”,断章取义,您现在可以停止坐在前廊并“处理事件”(包裹到达),无论何时。

【讨论】:

这是纯英语。 这似乎是最好的解释! +1 表示第一段。谢谢你。我发现除了处理事件之外,传递函数还有很多其他方法。例如,您可能需要一个通用排序例程,并将其传递给实际函数以比较记录。【参考方案11】:

没有回调也没有其他特殊的编程资源(如线程等),一个程序就是一个依次执行的指令序列,甚至具有一种“动态行为”由特定条件决定,所有可能的场景都应预先编程

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

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

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

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

我希望这个解释有用。

【讨论】:

【参考方案12】:

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

【讨论】:

更简单的解释是:我打电话给某人,她正在开会,我留下电话号码,她回电。【参考方案13】:

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

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

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

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

【讨论】:

【参考方案14】:

回调是将由第二个函数调用的函数。第二个函数事先并不知道它会调用什么函数。所以回调函数的identity被存储在某个地方,或者作为参数传递给第二个函数。这个“身份”取决于编程语言,可能是回调的地址,或者其他类型的指针,或者它可能是函数的名称。主体是相同的,我们存储或传递一些明确标识功能的信息。

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

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

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

【讨论】:

【参考方案15】:

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

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

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

这可能会在很多方面得到改进。例如,您可以提供第二次回拨:如果它最终花费的时间超过一个小时,请拨打红色电话并告诉接听者您已超时。

【讨论】:

【参考方案16】:

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

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

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

编辑:callback 这个词最常见的含义是一个函数,它作为参数传递给另一个函数,并在稍后的时间点被调用。这些是在允许高阶函数的语言中发现的想法,即将函数视为一等公民,它通常用于async 编程。 onready(dosomething)。这里dosomething 仅在它准备好时发生。

【讨论】:

还有什么类型的回调? @johnny:当 Ajax 完成等时会触发正常的浏览器回调。【参考方案17】:

想象一个朋友要离开你家,你告诉她“你到家后给我打电话,我知道你安全到了”;那是(字面意思)一个回调。这就是回调函数,与语言无关。您希望某个程序在完成某项任务后将控制权交还给您,因此您给它一个函数用于回调您。

例如,在 Python 中,

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

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

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,因为回调可以访问在定义它的 lexical 环境中声明的变量。词法作用域和匿名回调是一个强大的组合,值得新手程序员进一步研究。

【讨论】:

+1。我其实很喜欢这个答案。什么是回调的解释,简单明了。【参考方案18】:

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

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

可能还有更多的东西。让学生参与其中,他们会发现。希望这会有所帮助。

【讨论】:

我在programmers.SEprogrammers.stackexchange.com/a/75449/963中与此主题相关的另一个答案 在你的最后一个项目符号中,虽然术语回调在现代用于此,但我们所做的只是运行一个带有参数的函数,而该参数是另一个函数。 (如果有人关注堆栈,它甚至可能是它自己。)据我所知,这个构造比事件、回调和 GUI 构造要古老得多。它可以追溯到汇编语言,远在我们有 GUI 之前。我想我记得在 CPM 中看到过。【参考方案19】:

比喻解释:

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

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

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

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

选项 (b) 类似于回调。

【讨论】:

【参考方案20】:

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

起初回调只是普通的函数。 更进一步的是,我们从另一个函数(我们称其为 B)内部调用此函数(我们称其为 A)。

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

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

【讨论】:

【参考方案21】:

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

使用函数指针在 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没有函数指针的概念 它通过其Interface机制实现Callback机制 这里我们声明了一个接口,而不是函数指针,该接口有一个方法,当被调用者完成任务时将调用该方法

让我通过一个例子来演示一下:

回调接口

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)实现了这两个接口。并且每当一个widget发生事件时,都会调用更高级的代码(或者说在更高级的代码中实现的这些接口的方法,这里是Activity)。

现在让我讨论回调和事件监听器模式之间的基本区别。正如我们提到的,使用回调,被调用者只能通知一个调用者。但是在 EventListener 模式的情况下,应用程序的任何其他部分或类都可以注册可能发生在 Button 或 Checkbox 上的事件。这种类的例子是OtherClass。如果你看OtherClass的代码,你会发现它已经将自己注册为Activity中定义的Button中可能发生的ClickEvent的监听器。有趣的是,除了 Activity(调用者)之外,每当 Button 上发生点击事件时,也会通知这个 OtherClass。

【讨论】:

【参考方案22】:

在简单的英语中,回调是一个承诺。乔、简、大卫和萨曼莎拼车上班。乔今天开车。 Jane、David 和 Samantha 有几个选择:

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

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

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

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

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

希望有帮助!

【讨论】:

【参考方案23】:

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

【讨论】:

【参考方案24】:

什么是回调函数?

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

回调函数很难追踪,但有时它非常有用。尤其是在设计库时。回调函数就像是让你的用户给你一个函数名,你会在一定的条件下调用那个函数。

例如,您编写了一个回调计时器。它允许您指定持续时间和要调用的函数,并且该函数将相应地回调。 “每 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;   

【讨论】:

【参考方案25】:

回调是一种安排在满足条件时执行的方法。

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

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

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

【讨论】:

说它是“预定的”可能会在这里引起混淆。回调通常在异步系统中使用,不会有“调度”而是“事件”,它会触发回调被执行。【参考方案26】:

简单明了:回调是一个函数,您将其赋予另一个函数,以便它可以调用它。

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

【讨论】:

【参考方案27】:

现实生活中的例子

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

当我今天下午 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 需要一段时间(根据经验,网络被认为是 slow )。

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 尽可能粗鲁。

【讨论】:

【参考方案28】:

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

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

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

【讨论】:

【参考方案29】:

[edited]当我们有两个函数说functionA和functionB时,如果functionA依赖于functionB

然后我们将functionB称为callback function。这在Spring框架中被广泛使用。

【讨论】:

【参考方案30】:

把方法想象成给同事一个任务。一个简单的任务可能如下:

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

您的同事认真计算并为您提供以下结果:

x = -6
y = -4

但是你的同事有一个问题,他并不总是理解符号,例如^,但他确实通过它们的描述理解它们。如exponent。每次他找到其中一个时,您都会返回以下内容:

I don't understand "^"

这需要您在向您的同事解释该角色的含义后再次重写您的整个指令集,而他并不总是在两个问题之间记住。他也很难记住你的提示,比如问我。但是,他总是尽可能地遵循您的书面指示。

您想出了一个解决方案,只需将以下内容添加到您的所有说明中:

If you have any questions about symbols, call me at extension 1234 and I will tell you its name.

现在每当他有问题时,他都会打电话给你询问,而不是给你一个糟糕的回应并重新启动流程。

【讨论】:

以上是关于如何用简单的英语解释回调?它们与从另一个函数调用一个函数有何不同?的主要内容,如果未能解决你的问题,请参考以下文章

如何用python读取arcgis中shapefile文件的属性表

如何用javascript写个插件

调用从另一个类传递的回调

Javascript/Jquery 加载回调和编写基本函数

如何用php开启企业微信开发的回调模式

打字稿:你如何用布尔或回调函数定义联合类型?