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


当前回答

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

这两种方法都涉及到通过向现有函数传递附加功能(匿名或命名的函数定义)来定制函数。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,从而重新绘制浏览器窗口。

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

其他回答

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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