为什么我们需要“回调函数”?


16

我正在读书programming in Lua。它说

在许多情况下,封闭提供了一种有价值的工具。如我们所见,它们可用作排序等高阶函数的参数。闭包对于构建其他功能的功能也很有价值,例如我们的newCounter示例;这种机制允许Lua程序结合功能世界中的复杂编程技术。闭包对于回调函数也很有用。当您在传统的GUI工具箱中创建按钮时,就会出现一个典型的示例。每个按钮都有一个回调函数,当用户按下该按钮时会被调用。您希望不同的按钮在按下时会做一些略有不同的事情。例如,一个数字计算器需要十个类似的按钮,每个数字一个。您可以使用以下功能创建每个:

function digitButton (digit)
  return Button{label = tostring(digit),
                action = function ()
                  add_to_display(digit)
                  end}
end

看来,如果调用digitButton,它将返回action(将创建一个闭包),因此,我可以访问digit传递给digitButton

我的问题是:

Why we need call back functions? what situations can I apply this to?


作者说:

在此示例中,我们假定Button是创建新按钮的工具包函数。label是按钮标签;而action是按下按钮时要调用的回调闭包。在digitButton完成其任务之后以及在局部变量digit超出作用域之后,可以很长一段时间调用该回调,但是仍然可以访问此变量。

根据作者,我认为类似的例子是这样的:

function Button(t)
  -- maybe you should set the button here
  return t.action -- so that you can call this later
end

function add_to_display(digit)
  print ("Display the button label: " .. tostring(digit))
end

function digitButton(digit)
  return Button{label = tostring(digit),
                action = function ()
                           add_to_display(digit)
                           end}
end

click_action = digitButton(10)
click_action()

从而, the callback can be called a long time after digitButton did its task and after the local variable digit went out of scope.


Answers:


45

盖伊1到盖伊2:嘿,伙计,我想在用户单击那里时做点什么,等到发生时再打给我?

当用户单击此处时,盖伊2回叫盖伊1。


1
我非常喜欢这个回叫笑话:-P
Lucas Li

6
只有我吗?我不明白如何解释为什么我们需要回调函数。
phant0m 2013年

2
就像盖伊1对盖伊2所说的:“在适当的时候这样做”。盖伊2忙碌的一天,在适当的时候去做。如果您要让盖伊1成为盖伊2的调用者,那么这是不正确的,因为盖伊2并未回叫盖伊1,他会调用此事。实际上,如果盖伊1是盖伊2的调用者,那么在这种情况下,您将遇到困难。如果您想让盖伊1回叫,那是不正确的,因为回叫不知道盖伊2是谁,当然也不要求他打电话。
rism

这是一个可怕的解释。
Insidesin

21

不,它将永远不会返回该动作。当用户单击该按钮时,它将执行它。这就是答案。当您想要定义响应于您无法控制的事件而应在另一个组件中发生的动作时,您需要回调函数(该事件循环是系统的一部分,将执行该按钮的方法,该方法将依次执行那个行动)。


我不同意你的看法。我已经编辑了帖子并添加了一些代码。我仍然认为该动作不会立即执行。
Lucas Li

2
蒂姆,您说的没错-Jan也是。“当用户单击按钮时,按钮将执行它”,不是立即执行。
AlexanderBrevig

@AlexanderBrevig是的,我仍然认为Jan的回复对我非常有用。它使我知道什么是回叫以及为什么需要回叫。
卢卡斯·李

回调只是一个肮脏的名字,有人将其添加到了第二个函数中,该函数在第一个函数被触发时可能会根据条件起作用。调用函数well_done()不会有什么区别。回调就像验证,验证是消息序列,通常是两个,回调是函数的安全性,通常是两个。如果您链接了三个以上的回调,那么恭喜您,您喜欢用困难的方式做事。
m3nda '16

16

我认为使用回调的一个更好的例子是在异步函数中。我不能代表Lua,但是在JavaScript中,最常见的异步操作之一是通过AJAX与服务器联系。

AJAX调用是异步的,这意味着如果您执行以下操作:

var ajax = ajaxCall();
if(ajax == true) {
  // Do something
}

if块的内容将不能可靠地正确运行,而当它运行时,这仅仅是因为该ajaxCall()函数恰好在执行到达if语句之前完成了。

但是,回调可通过确保在调用所需函数之前完成异步调用来消除此问题。因此,您的代码将变为如下所示:

function ajaxCallback(response) {   
  if(response == true) {
    // Do something   
  }
}

ajaxCall(ajaxCallback);

这样做的目的是使您能够进行诸如收集数据之类的事情,而不会干扰诸如绘制界面之类的其他事情。如果数据收集是同步的,则接口将在应用程序等待获取数据时变得无响应。无响应的界面对用户体验非常不利,因为大多数用户会认为应用程序已“崩溃”并尝试终止该过程。

要了解这一点,您只需要查看可以进行任何更新而无需重新加载整个页面的任何网站。Twitter,LinkedIn和StackExchange网站就是很好的例子。Twitter的提要“无休止滚动”和SE的通知(用于用户通知以及问题有新活动的通知)均由异步调用提供支持。当您在某个地方看到微调框时,这意味着该部分已经进行了异步调用,并且正在等待该调用完成,但是您可以与界面上的其他内容进行交互(甚至可以进行其他异步调用)。该调用完成后,它将做出反应并相应地更新接口。


12

你问了这个问题

为什么我们需要“回调函数”?

我将尝试以简洁明了的方式回答(如果失败了,请参阅此答案底部的Wiki链接)。

回调用于将某些事物的特定实现推迟到最后可能的时刻。

按钮示例很好地说明了这一点。假设您想要在应用程序中使用一个按钮来将页面打印到打印机上,我们可以想象一个世界,在此世界中您必须编写一个全新的PrinterButton类才能执行此操作。

幸运的是,我们有回调的概念(继承和模板模式的使用也是一种回调语义),因此我们不需要这样做。

代替重新实现按钮的每个方面,我们要做的是添加到按钮的实现中。该Button有这样的概念的东西时,它的压制。我们只是告诉它什么是东西。

将行为注入框架的机制称为回调。

例:

让我们向HTML按钮中注入一些行为:

<button onclick="print()">Print page through a callback to the print() method</button>

在这种情况下,onclick指向回调,在此示例中为print()方法。


http://en.wikipedia.org/wiki/Callback_(computer_programming)


谢谢,阅读完您的插图后,我将阅读维基,其示例仅是同步回调。
卢卡斯·李

每次遇到问题时告诉函数答案,您必须倾听问题;教一个解决自己的问题的功能。

8

为什么我们需要回调函数?我可以在什么情况下应用?

回调函数在事件驱动的编程中非常有用。它们允许您以事件将触发正确代码的方式来设置程序。这在带有GUI的程序中非常常见,在该程序中,用户可以单击UI上的任何元素(例如按钮或菜单项),然后将运行适当的代码。


1
+这就是回调的优点,但是我讨厌不得不编写它们:-)
Mike Dunlavey 2013年

当我还是一名新生的时候,我的老师教我们如何在MFC中进行编程,但他从未告诉我们什么是回调:-(
Lucas Li

2

没有回调会发生什么:

盖伊1:好的,我正在等待那件事发生。(吹口哨,拨动拇指)

男人2:该死!为什么盖伊1没给我看东西?我想看看事情正在发生!我的东西呢?有人应该如何在这里做任何事情?


1

首先,术语回调是指用于某种事物的闭包。

例如,假设您创建了一个闭包函数并将其存储在变量中。它不是回调,因为它没有用于任何东西。

但是,假设您创建了一个闭包,并将其存储在发生某种情况时将被调用的位置。现在称为回调。

通常,回调是由程序的不同部分而不是调用它们的部分创建的。因此,很容易想象程序的某些部分正在“回调”其他部分。

简而言之,回调使程序的一部分可以在发生某些事情时告诉另一部分做某事(任何事情)。

至于由于在闭包中使用而使变量保持活动状态,则这是一个完全不同的功能,称为“ upvalues”(又称“延长局部变量的寿命”,而不讲Lua的人)。而且它也非常有用。


是的,Extending the lifetime of local variables这个名词也可以说upvalue
Lucas Li

嗯,有趣。该术语的upvalue似乎具体到Lua,虽然快速谷歌搜索表明,它偶尔被用来描述其他语言。我将其添加到我的答案中。
乔纳森·格雷夫

1

至少从Fortran成立以来,回调就已经存在了。例如,如果您有一个ODE(常微分方程)求解器,例如Runge-Kutta求解器,则它可能如下所示:

subroutine rungekutta(neq, t, tend, y, deriv)
  integer neq             ! number of differential equations
  double precision t      ! initial time
  double precision tend   ! final time
  double precision y(neq) ! state vector
  ! deriv is the callback function to evaluate the state derivative
  ...
  deriv(neq, t, y, yprime) ! call deriv to get the state derivative
  ...
  deriv(neq, t, y, yprime) ! call it several times
  ...
end subroutine

它使调用者可以通过在需要时向其传递一个专用函数来自定义子例程的行为。这使ODE求解器可用于模拟各种系统。


1

偶然发现此问题,并希望提供更多最新答案...

回调函数使我们可以在以后的时间对数据进行处理,从而使其余代码得以运行,而不是等待数据。异步代码使我们可以在以后执行任何操作。我遇到的最易读的回调函数示例是JavaScript中的Promises。在下面的示例中,每次看到function(result)或(newResult)或(finalResult) ...时,这些都是回调函数。数据从服务器返回后,将执行大括号内的代码。仅在这时执行这些功能才有意义,因为现在它们所需的数据已可用。

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

该代码取自... https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

希望这对某人有帮助。这就是帮助我理解回调的原因:)


1
在先前的9个答案中,似乎并没有提供任何实质性的要点和解释
gna

1
也许是给您的,但这确实使我清楚地知道了回调,所以我想我会分享。IMO的示例易读性使其值得。我敢肯定,我们可以同意代码的可读性会大大提高,尤其是对于初学者而言。
NRV

-2

我不知道lua,但是在一般的回调方法中,有些类似于多线程。例如,在移动应用程序开发编程中,大多数应用程序的功能都类似于向服务器发送请求,并根据服务器的响应来处理带有数据的UI。当用户向服务器发送请求时,从服务器获取响应将花费一些时间,但最好的UX UI不应卡住。

因此,我们使用多个线程来执行并行操作。当我们从服务器获得响应时,我们需要更新UI。我们必须从该线程通知以进行更新。来自功能世界。这种类型的函数调用称为回调方法。当您调用这些方法时,控件应该返回主线程。例如,回调方法是Objective-C中的块。


是的,您所说的与我阅读本书后的理解相符。
Lucas Li

2
回调方法与多线程不同。回调方法只是函数指针(代理)。线程与CPU上下文切换/利用有关。高度简化的对比是,线程是关于UX的CPU优化,而回调是关于多态的内存切换。仅仅因为线程可能执行回调并不意味着线程和回调是相似的。线程还在静态类上执行静态方法,但是线程和静态类型并不相似。
rism
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.