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

如何用简单的英语解释回调?它们与从一个函数调用另一个函数从调用函数获取上下文有什么不同?如何向新手程序员解释它们的强大功能?

205990 次浏览

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

假设,你想在被作为参数给出之前处理它:function1(var1, function2(var2))

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

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

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

假设有两个模块A和B。

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

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

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

当我们有两个函数函数a和函数b时,如果功能阳极依赖于functionB

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

回调函数维基百科的例子

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

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

你觉得不舒服,所以去看医生。他检查了你的身体,认为你需要一些药物治疗。他开了一些药,并把处方打电话到你当地的药店。你回家吧。稍后,你的药房打电话告诉你,你的处方已经准备好了。你去捡吧。

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

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

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

想象一下,一个朋友要离开你的家,你告诉她“到家后给我打个电话,好让我知道你已经安全到家了”;它(字面上)是回电话。这就是回调函数,与语言无关。您希望某个过程在完成某些任务后将控制传递回给您,因此您可以给它一个函数,用于回调。

例如,在Python中,

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

grabDBValue可以被编写为只从数据库中获取一个值,然后让你指定对该值实际做什么,因此它接受一个函数。你不知道grabDBValue何时或是否会返回,但如果/当它返回时,你知道你想让它做什么。在这里,我传入一个匿名函数(或λ),它将值发送到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,因为回调可以访问定义它的词汇环境中声明的变量。词法作用域和匿名回调是一个强大的组合,值得新手进一步研究。

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

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

这是关于下载网页的:

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

基本上,它允许你请求一些东西,并在等待结果的同时继续执行。一旦结果通过回调函数返回给您,您就可以从它停止的地方开始操作。

隐喻性的解释:

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

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

我可以在邮局等邮件送到。

(b)邮件送达后我会收到电子邮件。

选项(b)类似于回调。

我要尽量让这个问题简单化。“回调”是由另一个以第一个函数为参数的函数调用的任何函数。很多时候,“回调”是在某物发生时调用的函数。在编程语言中,某物可以被称为“事件”。

想象一下这样的场景:你几天后就会收到一个包裹。这个包裹是给邻居的礼物。因此,一旦你得到了包裹,你想把它带到邻居那里。你出城了,所以你给你的配偶留下了指示。

你可以让他们去拿包裹,然后送到邻居那里。如果你的配偶像电脑一样愚蠢,他们会坐在门口等包裹来(什么也不做),然后一旦它来了,他们就会把它带到邻居那里。但是有一个更好的办法。告诉你的配偶,一旦他们收到包裹,他们应该把它带到邻居那里。然后,他们可以正常生活,直到他们收到包裹。

在我们的例子中,包的接收是“事件”,而把它带给邻居是“回调”。你的配偶“运行”你的指令,只带包裹到达。更好的!

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

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来获得一些这种思考的实际练习。

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

用其他答案用过的等待包裹到达的比喻来解释这两个问题是很好的。在计算机程序中,你会告诉计算机期待一个包裹。通常情况下,它现在会坐在那里等待(什么也不做),直到包裹到达,如果包裹从未到达,它可能会无限期地等待。对人类来说,这听起来很愚蠢,但如果没有进一步的措施,这对计算机来说是完全自然的。

现在回调是你前门的铃声。你为包裹服务提供了一种通知你包裹到达的方式,而不需要他们知道你在房子的哪里(即使),或者铃声是如何工作的。(例如,一些“铃声”实际上是发送一个电话。)因为你提供了一个“回调函数”,可以在任何时候被“调用”,脱离上下文,你现在可以不用坐在门廊前,随时“处理事件”(包裹到达)。

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

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

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

使用回调:Ask for Information->Process Information->继续执行其他操作。在稍后的某个点->处理处理的结果。

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

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

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

一个“真实世界”的例子是当地的电子游戏商店。你在等待《半条命3》。你不必每天去商店查看游戏是否上架,而是在列表中注册电子邮件,以便在游戏上架时收到通知。这封邮件变成了你的“回调”,你需要满足的条件是游戏的可用性。

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

回调是一种异步处理事件的方法。你永远不知道回调什么时候会被执行,或者它是否会被执行。这样做的好处是可以释放程序和CPU周期,以便在等待应答时执行其他任务。

说白了,回调就是承诺。乔、简、大卫和萨曼莎拼车去上班。乔今天开车。简,大卫和萨曼莎有几个选择:

  1. 每五分钟查看一下窗户,看看乔是否出去了
  2. 继续做他们的事,直到乔按门铃。

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

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

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

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

希望有帮助!

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

在简单的英语中,回调函数就像工人,当他完成任务时“回调”他的经理

它们与从一个函数调用另一个函数有什么不同 从调用函数中获取一些上下文?< / p >

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

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

在需要从服务器获取数据的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,这就像工人在完成任务之前需要从经理获取信息一样。要创建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);

关闭

最后,下面是道格拉斯Crockfordclosure的定义:

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

参见:

  • http://javascript.crockford.com/survey.html < a href = " http://javascript.crockford.com/survey.html " > < / >
  • http://api.jquery.com/jQuery.when/ < a href = " http://api.jquery.com/jQuery.when/ " > < / >
  • http://api.jquery.com/jQuery.getJSON/ < a href = " http://api.jquery.com/jQuery.getJSON/ " > < / >
  • http://github.com/josher19/jQuery-Parse < a href = " http://github.com/josher19/jQuery-Parse " > < / >

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

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

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

这是关键。您不需要更改表单(代码,通常是其他人的)。然而,你可以填写缺失的信息(你的数字)。

示例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”写在紧急号码栏里。这没什么意义,但这是允许的。


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

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

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

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

回调是一个将被第二个函数调用的函数。第二个函数事先不知道它将调用哪个函数。所以回调函数的身份被存储在某个地方,或者作为参数传递给第二个函数。这个“标识”(取决于编程语言)可能是回调函数的地址,或者其他类型的指针,也可能是函数的名称。原理是一样的,我们存储或传递一些明确标识函数的信息。

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

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

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

没有回调,也没有其他特殊的编程资源(如线程等),程序就是一个指令序列,一个接一个地按顺序执行,甚至有一种由某些条件决定的“动态行为”,所有可能的情况都应事先编制好程序

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

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

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

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

我希望这个解释对你有用。

让我们假设您要给我一个可能长期运行的任务:获取您遇到的前五个独特的人的名字。如果我在人口稀少的地区,这可能需要几天的时间。你真的不想在我忙来忙去的时候袖手旁观,所以你说:“你拿到名单后,打我手机给我读一遍。这是号码。”

您已经给了我一个回调引用——一个我应该执行以传递进一步处理的函数。

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

这可能有很多方面可以改进。例如,你可以提供第二次回拨:如果最终超过一个小时,打电话给红色电话,告诉接听电话的人你超时了。

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

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

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

我认为这很容易解释。

最初的回调只是普通的函数 更进一步说,我们从另一个函数(我们称它为B)内部调用这个函数(我们称它为A)

神奇的是,我决定,哪一个函数应该由 B中的函数调用。

在我写函数B的时候,我不知道应该调用哪个回调函数。 当我调用函数B时,我也告诉这个函数调用函数a,这就是全部

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

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.

现在,无论何时他有问题,他都会打电话问你,而不是给你一个糟糕的回复,让整个过程重新开始。

什么是回调函数?

对第一个问题的简单回答是,回调函数是通过函数指针调用的函数。如果你将一个函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用它所指向的函数时,就被称为回调。

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

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

用电话系统来描述回调是最容易的。函数调用类似于打电话给某人,问她一个问题,得到答案,然后挂断电话;添加回调改变了这个类比,这样在问她一个问题之后,你也可以告诉她你的名字和电话号码,这样她就可以给你回电话,告诉你答案。——Paul Jakubik , "回调实现在c++ "

在PHP中,它是这样的:

<?php


function string($string, $callback) {
$results = array(
'upper' => strtoupper($string),
'lower' => strtolower($string),
);


if(is_callable($callback)) {
call_user_func($callback, $results);
}
}


string('Alex', function($name) {
echo $name['lower'];
});

我很震惊地看到这么多聪明的人都没有强调“回调”这个词已经有了两种不一致的用法。

这两种方法都涉及到通过向现有函数传递附加功能(匿名或命名的函数定义)来定制函数。ie。

customizableFunc(customFunctionality)

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

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

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

但这与在AJAX或node.js中使用异步编程的“回调”函数或简单地将功能分配给用户交互事件(如鼠标单击)有本质区别。在这种情况下,整个思想是在执行定制功能之前等待偶然事件发生。这在用户交互的情况下是显而易见的,但在i/o(输入/输出)过程中也很重要,这些过程可能会花费时间,比如从磁盘读取文件。这就是术语“回调”最明显的意义所在。一旦i/o进程启动(比如请求从磁盘读取文件或请求服务器从http请求返回数据),异步程序就不会等待它完成。它可以继续执行接下来计划的任何任务,只有在收到读文件或http请求完成(或失败)以及数据可用于自定义功能的通知后,才响应自定义功能。这就像给一家公司打电话,留下你的“回叫”号码,这样他们就可以在有人有空的时候给你打电话。这总比不知道要等多久而不能处理其他事情要好。

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

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

回调函数:

我们定义了一个名为callbackcallback function,给它一个参数otherFunction,并在函数体中调用它。

function callback(otherFunction){
otherFunction();
}

当调用callback函数时,它需要一个类型为function的实参,因此我们使用匿名函数调用它。但是,如果实参不是function类型,则会产生错误。

callback(function(){console.log('SUCCESS!')});
callback(1); // error

烤比萨饼的例子。 烤炉 披萨底,上面有配料 现在这里,ovencallback function。 和pizza base with ingredientsotherFunction.

需要注意的一点是不同的披萨原料产生不同类型的披萨,但烘焙披萨的烤箱保持不变。 这在某种程度上是callback function的工作,它不断期待具有不同功能的函数,以产生不同的自定义结果

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

在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个观察者/监听器某个特定任务已经完成

  • 列表项

回调机制和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;
}
}

Activity类

包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组件(如按钮、复选框)的基类。这些UI组件是实际从框架代码接收事件的对象。Widget类实现了Events接口,它也有两个嵌套接口,即OnClickEventListener和amp;OnLongClickEventListener

这两个接口负责监听Widget派生的UI组件(如Button或Checkbox)上可能发生的事件。因此,如果我们将这个示例与前面使用Java Interface的回调示例进行比较,这两个接口可以作为回调接口。因此,高级代码(Here Activity)实现了这两个接口。当一个事件发生在一个小部件上时,高级代码(或者在高级代码中实现的这些接口的方法,这里是Activity)将被调用。

现在让我讨论一下Callback和Eventlistener模式之间的基本区别。正如我们已经提到的使用Callback, Callee只能通知一个Caller。但是在EventListener模式的情况下,应用程序的任何其他部分或类都可以为按钮或复选框上可能发生的事件注册。这类类的例子是OtherClass。如果您看到OtherClass的代码,您会发现它已经将自己注册为活动中定义的Button中可能发生的ClickEvent的监听器。有趣的是,除了Activity (Caller),这个OtherClass还将在按钮上发生click事件时得到通知。

现实生活中的例子

这里有一个真实的例子,嗯,我自己的生活。

当我下午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非常粗鲁。

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年得到承诺&Async /await在~2017。在此之前,使用回调。

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

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

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

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

回调并不意味着闭包!

虽然通常用作提供闭包的一种简洁方式,但我的示例并不是这样。

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

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

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