如何用简单的英语解释回调?它们与从一个函数调用另一个函数从调用函数获取上下文有什么不同?如何向新手程序员解释它们的强大功能?
当前回答
假设你需要一个函数返回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)。
除了简化代码之外,它还让异步代码通过在完成时调用任意函数来让您知道它已经完成,这与打电话给某人并留下回调号码类似。
其他回答
从一个例子开始总是更好的:)。
假设有两个模块A和B。
你希望模块A在模块B中发生某些事件/条件时得到通知。然而,模块B对模块A一无所知。它只知道模块A提供给它的函数指针指向模块A的特定函数的地址。
因此,所有B现在必须做的是,当一个特定的事件/条件发生时,使用函数指针“回调”到模块A。A可以在回调函数内部进行进一步处理。
这里一个明显的优点是,你从模块B中抽象出了模块A的所有内容。模块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.
现在,无论何时他有问题,他都会打电话问你,而不是给你一个糟糕的回复,让整个过程重新开始。
应用程序通常需要根据其上下文/状态执行不同的功能。为此,我们使用一个变量来存储关于要调用的函数的信息。根据需要,应用程序将使用要调用的函数的信息来设置这个变量,并使用相同的变量来调用该函数。
在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]
回呼是一个贴了邮票的回邮信封。当你调用一个函数时,就像发送一封信一样。如果您希望该函数调用另一个函数,则以引用或地址的形式提供该信息。
在非程序员的术语中,回调是程序中的填空。
许多纸质表格上常见的一项是“紧急情况下的电话号码”。这里有一个空行。你写上某人的名字和电话号码。如果发生紧急情况,就会打电话给那个人。
每个人都有相同的空白表格,但是 每个人都可以写不同的紧急联系电话。
这是关键。您不需要更改表单(代码,通常是其他人的)。不过,你可以填写缺失的信息(你的电话号码)。
示例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”写在紧急号码栏里。这没什么意义,但这是允许的。
最后提示:用回调填充的空行?它可以被随意擦除和重写。(你是否应该是另一个问题,但这是他们权力的一部分)