【问题标题】:What is the difference between callback, high-order functions, and callback queue回调、高阶函数、回调队列有什么区别
【发布时间】:2019-01-05 20:18:06
【问题描述】:

此时,我有一个问题:回调到底是什么,它与高阶函数有何不同,以及它与概念有何关系回调队列

来自 MDN:Callback function

回调函数是作为参数传递给另一个函数的函数,然后在外部函数内部调用该函数以完成某种例程或动作。

看起来与高阶函数的定义有重叠。一个被传递(然后被调用)给另一个函数的函数。

这是回调的 MDN 示例:

function greeting(name) {
  alert('Hello ' + name);
}

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

processUserInput(greeting);

到目前为止毫无疑问。

然后我遇到了事件循环回调队列的想法。

 console.log("me first");

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

  console.log("me second")

原来seTimeout 函数实际上是一个JavaScript 包装器,它在后台与Web 浏览器API(计时器)进行接口。 setTimeout 将一个函数 (asyncLog) 和计时器 (2000ms) 传递给 Timer API。

当计时器功能(在 Web 浏览器中)完成其工作时,将在 JS 调用堆栈为 (1) 空时发送 callback queue 中的函数 asyncLog 准备在调用堆栈中调用(2) 已经处理了全局执行上下文中的所有内容。

所以在处理完最后一行console.log("me second") 之后,事件循环将回调函数asyncLog 从回调队列传递到调用堆栈,并执行它。

最后的顺序是:

me first
me second
i am the last

在第一个示例中,尽管我们将 greeting 称为“回调”函数,但我的理解表明回调队列的整个机制被完全跳过:我们没有做任何异步的事情,我们也没有与 Web 浏览器 API 接口(一切都包含在 JS 中)。

如果是这样,为什么我们将传递给其他函数的函数称为回调(而不是简单的高阶函数),而它们与回调队列和异步世界无关?

【问题讨论】:

  • 高阶函数 (HOF) 是返回函数的函数,而不是简单值。回调是一个函数,一旦(通常是异步的)过程完成,它就会被其他东西“回调”;回调可以返回一个值或一个函数,或者什么都不返回。因此,回调可能也可能不是 HOF,具体取决于回调它的 API。我不清楚你在哪里看到了冲突。
  • @JonasW。我指的是问候功能。
  • 是的,第一个例子不是异步的,回调是立即调用的。回调可能会或可能不会最终被放置在回调队列中,具体取决于调用它的内容。我仍然不确定这如何导致您的结论“如果是这样......”
  • 没关系,看来我得仔细阅读了。
  • @jonrsharpe 说得通乔纳森。只有当 Web 浏览器 API 将函数“推送”到回调队列中时,它才会碰巧出现在回调队列中? (就像在 setTimeout 示例中一样)。

标签: javascript callback functional-programming


【解决方案1】:

higher-order function 是一个将另一个函数作为参数的函数和/或将一个函数返回给它的调用者。

callback function 是一个传递给另一个函数的函数,期望另一个函数调用它。

所以回调本身不一定是高阶函数,而是接收回调作为参数的函数。考虑一个非常常见的情况,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.

在现实生活中,由于诸如微任务解析之类的事情(以及当排队的回调排队另一个回调时会发生什么),这比这要复杂一些,但这应该会给你一个像样的画面。

【讨论】:

    【解决方案2】:

    高阶函数是将另一个函数作为参数和/或将函数返回给其调用者的函数。这里,作为参数传递的函数被称为回调函数。 但是,回调函数也可能是高阶函数,也可能不是。让我们看看例子。

    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编译器在事件循环的帮助下完美执行异步和回调操作的机制。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-02-20
      • 2015-03-31
      • 2014-02-23
      • 1970-01-01
      • 1970-01-01
      • 2021-04-29
      • 1970-01-01
      相关资源
      最近更新 更多