回调函数、高阶函数和回调队列的区别是什么?

11
在这一点上,我有一个问题,即什么是“回调函数”,它与“高阶函数”有何不同,并且它如何与“回调队列”的概念相关?
来自 MDN:回调函数 回调函数是作为参数传递给另一个函数的函数,然后在外部函数内被调用以完成某种例行程序或操作。
看起来这个定义与高阶函数的定义有重叠。高阶函数是指传递(稍后调用)到另一个函数中的函数。
这是 MDN 回调函数示例:
function greeting(name) {
  alert('Hello ' + name);
}

function processUserInput(callback) {
  var name = prompt('Please enter your name.');
  callback(name);
}

processUserInput(greeting);

毫无疑问。

接下来我面对了 事件循环(Event Loop)回调队列(Callback queue) 的概念。

 console.log("me first");

  setTimeout(function asyncLog() {
       console.log("i am the last")
  }, 2000);

  console.log("me second")
Turns out the setTimeout function is a JavaScript wrapper that interfaces with the Web Browser API (timer) under the hood. When called, it passes a function (asyncLog) and a timer value (2000ms) to the Timer API. After the timer completes its work, it sends the asyncLog function to the callback queue in the web browser. This function will be invoked in the call stack as soon as the JS call stack is empty and has processed everything in the global execution context. Finally, after the last line of code (console.log("me second")) is processed, the Event Loop passes the asyncLog function from the callback queue to the call stack and executes it. The final order is:
me first
me second
i am the last
在第一个例子中,尽管我们将greeting称为“回调”函数,但我的理解表明整个“回调队列”机制完全被跳过了:我们没有进行任何异步操作,也没有与Web浏览器API进行交互(一切都被封装在JS中)。
如果是这样,那么为什么当我们将函数传递到其他函数中时,要将它们称为回调(而不是简单的高阶函数),当它们与回调队列和异步世界毫无关系时?

2
高阶函数(HOF)是指返回函数而不是简单值的函数。回调函数是一种在通常是异步过程完成后由其他东西“回调”的函数;回调函数可以返回一个值、一个函数或什么也不返回。因此,根据调用它的任何东西的API,回调函数可能也可能不是HOF。我不清楚您在哪里看到了冲突。 - jonrsharpe
2
是的,第一个例子不是异步的,回调会立即被调用。回调可能会或可能不会最终被放置在回调队列中,这取决于谁调用它。我仍然不确定这如何导致您的结论“如果是这样……”。 - jonrsharpe
没事了,看来我需要更仔细地阅读。 - Jonas Wilms
嗨,Leonardofed,希望你已经解决了这个问题。我问了一个类似的问题,也感到困惑,我猜只有像Web API那样信号事件的回调函数才会被推送到这样的队列中:https://stackoverflow.com/questions/53919515/javascript-callback-function-not-executing-as-intended - mzoz
@mzoz 是的,我看到了你的问题。不要混淆两者。cb是传递给HOF并在稍后调用的基本函数。它们不一定是异步的。 - leonardofed
显示剩余7条评论
2个回答

24

高阶函数是一种接受其他函数作为参数和/或返回函数给调用者的函数。

回调函数是一种传递给另一个函数并期望该函数调用它的函数。

因此,回调函数本身不一定是高阶函数,但一个接收回调函数作为参数的函数却是高阶函数。考虑一个非常常见的情况,即DOM事件监听器:

elem.addEventListener('click', console.log);

在这里,.addEventListener 是一个高阶函数,它接受另一个函数 (console.log) 并调用它。虽然在这里 console.log 是一个回调函数,但在这种情况下它本身并不是一个高阶函数。
事件循环是底层运行时向您公开的一种机制。在这里,我们将想象手动执行此操作,使用数组作为队列:
const queue = [];
const setTimeout = (f, ...args) => {
  queue.push([f, args]);
};

const runQueuedCallbacks = () => {
  let queued;
  while (queued = queue.shift()) {
    let [f, args] = queued;
    f(...args);
  }
};

setTimeout(console.log, 'first');   // queues a call to log
setTimeout(console.log, 'second');  // queues a call to log
console.log('zero-th');             // directly calls log, prints first

// at this point there's no more code to execute, so runQueuedCallbacks runs
// and sequentially steps through the enqueued callbacks.

在现实生活中,由于微任务分辨率(还有当排队的回调队列另一个回调时会发生什么)等因素,情况比这更加复杂,但这应该能给你一个不错的概念。


0
高阶函数是指接受一个或多个函数作为参数并/或向其调用者返回一个函数的函数。在这里,作为参数传递的函数被称为回调函数。 然而,回调函数也可能是高阶函数,也可能不是。让我们看一些例子。
function printString(callbackHof, callback_only, str) {
   str +=' concated first';
  callbackHof( callback_only,str);
}

function concatFirst(callback_only, str)
{
  callback_only(str);
}

function concatAgain(str)
{
  str += ' contated again';
  console.log(str);
}

printString(concatFirst, concatAgain, 'anything');

输出结果是='anything concated first contated again' 以便澄清。

这里的printString()是一个高阶函数,它接受两个函数和一个字符串作为参数。printString()函数的参数是concatFirst()和concatAgain(),这些concatFirst()和concatAgain()函数都是回调函数。

在这里,concatFirst()既是回调函数又是高阶函数,因为它作为printString()的参数传递,然后还将concatAgain()回调函数作为自己的输入。

而concatAgain()只是一个回调函数,因为它只用一个字符串参数调用,没有更进一步的函数参数。

最后,回调队列是浏览器或JavaScript编译器遵循的机制,通过事件循环完美地执行异步和回调操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接