如何用简单的英语解释回调?它们与从一个函数调用另一个函数从调用函数获取上下文有什么不同?如何向新手程序员解释它们的强大功能?
通常,我们将变量发送给函数:function1(var1, var2)。
假设,你想在它被作为参数给出之前处理它:
这是一种回调类型,其中function2执行一些代码并将变量返回给初始函数。
编辑:回调这个词最常见的意思是一个函数作为参数传递给另一个函数,并在稍后的时间点被调用。这些思想存在于允许高阶函数的语言中,即将函数视为一等公民,通常用于异步编程中。onready dosomething()。在这里,只有当它准备好了,事情才会发生。
从一个例子开始总是更好的:)。
假设有两个模块A和B。
你希望模块A在模块B中发生某些事件/条件时得到通知。然而,模块B对模块A一无所知。它只知道模块A提供给它的函数指针指向模块A的特定函数的地址。
因此,所有B现在必须做的是,当一个特定的事件/条件发生时,使用函数指针“回调”到模块A。A可以在回调函数内部进行进一步处理。
这里一个明显的优点是,你从模块B中抽象出了模块A的所有内容。模块B不必关心模块A是谁/什么。
应用程序通常需要根据其上下文/状态执行不同的功能。为此,我们使用一个变量来存储关于要调用的函数的信息。根据需要,应用程序将使用要调用的函数的信息来设置这个变量,并使用相同的变量来调用该函数。
在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何时或是否会返回,但是如果/当它返回时,您知道希望它做什么。在这里,我传入一个匿名函数(或lambda),它将值发送到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)。
除了简化代码之外,它还让异步代码通过在完成时调用任意函数来让您知道它已经完成,这与打电话给某人并留下回调号码类似。
这是关于下载网页的:
Your program runs on a cellphone and is requesting the webpage http://www.google.com. If you write your program synchronously, the function you write to download the data will be running continuously until all the data is download. This means your UI will not refresh and will basically appear frozen. If you write your program using callbacks, you request the data and say "execute this function when you've finished." This allows the UI to still allow user interaction while the file is downloading. Once the webpage has finished downloading, your result function (callback) is called and you can handle the data.
基本上,它允许你请求一些东西,并在等待结果的同时继续执行。一旦结果通过回调函数返回给您,您就可以从它停止的地方开始操作。
隐喻性的解释:
我有一个包裹要寄给一个朋友,我也想知道我的朋友什么时候收到。
所以我把包裹带到邮局,让他们把它送到。如果我想知道我的朋友什么时候收到包裹,我有两个选择:
我可以在邮局等邮件送到。
(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:这更像是一个轮询示例,Jane将被困在一个“循环”中,检查Joe是否在室外。在此期间简不能做其他事情。
选项2:这是回调示例。简告诉乔当他在外面时按她的门铃。她给了他一个按门铃的“功能”。Joe不需要知道门铃是如何工作的,也不需要知道它在哪里,他只需要调用那个函数,即当他在那里时按门铃。
回调是由“事件”驱动的。在这个例子中,“事件”是乔的到来。例如,在Ajax中,事件可以是异步请求的“成功”或“失败”,并且每个事件都可以有相同或不同的回调。
In terms of JavaScript applications and callbacks. We also need to understand "closures" and application context. What "this" refers to can easily confuse JavaScript developers. In this example within each person's "ring_the_door_bell()" method/callback there might be some other methods that each person need to do based on their morning routine ex. "turn_off_the_tv()". We would want "this" to refer to the "Jane" object or the "David" object so that each can setup whatever else they need done before Joe picks them up. This is where setting up the callback with Joe requires parodying the method so that "this" refers to the right object.
希望有帮助!
如何用简单的英语解释回调?
简单地说,回调函数就像一个Worker,当他完成一个任务时,他就会“回调”给他的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
}
关闭:
通常,回调需要使用闭包从调用函数访问状态,这就像Worker在完成任务之前需要从Manager获取信息一样。要创建闭包,你可以内联函数,这样它就可以看到调用上下文中的数据:
/* 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对闭包的定义:
函数可以在其他函数内部定义。内部函数可以访问外部函数的变量和参数。如果对内部函数的引用仍然存在(例如,作为回调函数),则外部函数的变量也仍然存在。
参见:
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:
回调被用作自定义方法,可能用于添加/更改程序的行为。例如,一些C代码执行一个函数,但不知道如何打印输出。它所能做的就是创造一个字符串。当它试图弄清楚该如何处理字符串时,它看到了一个空行。但是,程序员给了你写回调的空白!
在这个例子中,你不用铅笔在纸上填空白,你使用函数set_print_callback(the_callback)。
模块/代码中的空白变量是空行, Set_print_callback是铅笔, the_callback是你要填写的信息。
现在您已经在程序中填充了这一行空白。当它需要打印输出时,它将查看空白行,并遵循那里的指示(即调用您放在那里的函数)。实际上,这允许打印到屏幕、日志文件、打印机、通过网络连接或它们的任何组合。你已经填满了你想做的事情。
示例2:
当你被告知需要拨打一个紧急电话号码时,你去阅读纸质表格上写的内容,然后拨打你读到的号码。如果这一行是空的,什么也不会做。
Gui编程的工作原理与此大致相同。当一个按钮被点击时,程序需要弄清楚下一步要做什么。它去寻找回调。这个回调恰好在一个空白中,标签是"点击Button1时你所做的事情"
大多数ide会在你要求它(例如button1_clicked)时自动为你填充空白(编写基本方法)。不过那个空白可以有任何方法你都可以补好。你可以调用方法run_computices或butter_the_biscuits,只要你把那个回调的名字放在适当的空格里。你可以把“555-555-1212”写在紧急号码栏里。这没什么意义,但这是允许的。
最后提示:用回调填充的空行?它可以被随意擦除和重写。(你是否应该是另一个问题,但这是他们权力的一部分)
为了教授回调,你必须先教授指针。一旦学生理解了指向变量的指针的概念,回调的概念就会变得更容易。假设您使用的是C/ c++,可以遵循这些步骤。
First show your students how to use and manipulate variables using pointers alongside using the normal variable identifiers. Then teach them there are things that can be done only with pointers(like passing a variable by reference). Then tell them how executable code or functions are just like some other data(or variables) in the memory. So, functions also have addresses or pointers. Then show them how functions can be called with function pointers and tell these are called callbacks. Now, the question is, why all these hassle for calling some functions? What is the benefit? Like data pointers, function pointer aka callbacks has some advantages over using normal identifiers. The first one is, function identifiers or function names cannot be used as normal data. I mean, you cannot make a data structure with functions(like an array or a linked list of functions). But with callbacks, you can make an array, a linked list or use them with other data like in dictionary of key-value pairs or trees, or any other things. This is a powerful benefit. And other benefits are actually child of this one. The most common use of callbacks is seen in event driver programming. Where one or more functions are executed based on some incoming signal. With callbacks, a dictionary can be maintained to map signals with callbacks. Then the input signal resolution and execution of corresponding code become much easier. The second use of callbacks coming in my mind is higher order functions. The functions which takes other functions as input arguments. And to send functions as arguments, we need callbacks. An example can be a function which take an array and a callback. Then it performs the callback on each of the item of the array and return the results in another array. If we pass the function a doubling callback, we get a doubled valued array. If we pass a squaring callback, we get squares. For square roots, just send appropriate callback. This cannot be done with normal functions.
可能还有更多的事情。让学生参与进来,他们就会发现。希望这能有所帮助。
A callback is a function that will be called by a second function. This second function doesn't know in advance what function it will call. So the identity of the callback function is stored somewhere, or passed to the second function as a parameter. This "identity," depending on the programming language, might be the address of the callback, or some other sort of pointer, or it might be the name of the function. The principal is the same, we store or pass some information that unambiguously identifies the function.
当时间到来时,第二个函数可以调用回调,根据当时的情况提供参数。它甚至可以从一组可能的回调中选择回调。编程语言必须提供某种语法,允许第二个函数调用回调函数,并知道它的“标识”。
这种机制有许多可能的用途。通过回调,函数的设计者可以通过调用所提供的任何回调来定制函数。例如,排序函数可能将回调函数作为参数,而这个回调函数可能是用于比较两个元素以决定哪个元素先出现的函数。
顺便说一下,根据编程语言的不同,上面讨论中的“函数”一词可能会被“块”、“闭包”、“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
但是你的同事有一个问题,他并不总是理解符号,比如^,但是他通过它们的描述理解它们。如指数。每次他找到其中一个,你就会得到以下信息:
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;
}
用电话系统来描述回调是最容易的。函数调用类似于打电话给某人,问她一个问题,得到答案,然后挂断电话;添加回调改变了这个类比,这样在问她一个问题之后,你也可以告诉她你的名字和电话号码,这样她就可以给你回电话,告诉你答案。——保罗·雅库比 、《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方法,其中提供了一个自定义函数作为参数,应用于数组中的每个元素以修改数组。
But this is fundamentally distinct from the use of "callback" functions for asynchronous programming, as in AJAX or node.js or simply in assigning functionality to user interaction events (like mouse clicks). In this case, the whole idea is to wait for a contingent event to occur before executing the custom functionality. This is obvious in the case of user interaction, but is also important in i/o (input/output) processes that can take time, like reading files from disk. This is where the term "callback" makes the most obvious sense. Once an i/o process is started (like asking for a file to be read from disk or a server to return data from an http request) an asynchronous program doesn't wait around for it to finish. It can go ahead with whatever tasks are scheduled next, and only respond with the custom functionality after it has been notified that the read file or http request is completed (or that it failed) and that the data is available to the custom functionality. It's like calling a business on the phone and leaving your "callback" number, so they can call you when someone is available to get back to you. That's better than hanging on the line for who knows how long and not being able to attend to other affairs.
异步使用本质上涉及到一些侦听所需事件的方法(例如,i/o进程的完成),以便当它发生时(且仅当它发生时)执行自定义的“回调”功能。在明显的AJAX示例中,当数据实际从服务器到达时,“回调”函数将被触发,以使用该数据修改DOM,从而重新绘制浏览器窗口。
回顾一下。有些人使用“回调”这个词来指代任何可以作为参数注入到现有函数中的自定义功能。但是,至少对我来说,这个词最合适的用法是异步使用注入的“回调”函数——仅在等待通知的事件发生时执行。
回调函数:
我们定义了一个名为callback的回调函数,给它一个参数otherFunction,并在函数体中调用它。
function callback(otherFunction){
otherFunction();
}
当我们调用回调函数时,它需要一个类型为函数的参数,因此我们使用匿名函数调用它。但是,如果实参不是function类型,则会产生错误。
callback(function(){console.log('SUCCESS!')});
callback(1); // error
烤披萨的例子。 烤炉 披萨底,上面有配料 这里,烤箱是回调函数。 披萨的配料是另一个功能。
值得注意的是,不同的披萨原料可以做出不同类型的披萨,但烘焙披萨的烤箱是一样的。 这在某种程度上是回调函数的工作,它不断期望具有不同功能的函数,以产生不同的自定义结果。
在计算机编程中,回调是对可执行代码或一段可执行代码的引用,它作为参数传递给其他代码。这允许较低级别的软件层调用较高级别的软件层定义的子例程(或函数)。——维基百科
在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和OnLongClickEventListener
These two interfaces are responsible for listening to events that may occur on the Widget derived UI components like Button or Checkbox. So if we compare this example with the earlier Callback example using Java Interface, these two interfaces work as the Callback interface. So the higher level code (Here Activity) implements these two interfaces. And whenever an event occurs to a widget, the higher level code (or the method of these interfaces implemented in the higher level code, which is here Activity) will be called.
Now let me discuss the basic difference between Callback and Eventlistener pattern. As we have mentioned that using Callback, the Callee can notify only a single Caller. But in the case of EventListener pattern, any other part or class of the Application can register for the events that may occur on the Button or Checkbox. The example of this kind of class is the OtherClass. If you see the code of the OtherClass, you will find that it has registered itself as a listener to the ClickEvent that may occur in the Button defined in the Activity. Interesting part is that, besides the Activity ( the Caller), this OtherClass will also be notified whenever the click event occurs on the Button.
现实生活中的例子
这里有一个真实的例子,嗯,我自己的生活。
当我下午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非常粗鲁。
回调函数是作为参数传递给另一个函数的函数(在某些时候使用)。
以下是一些函数:
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语言可以使用函数指针作为回调函数,尽管它没有第一类函数。