原文:What is a Callback Function in JavaScript?

本文将简要介绍 JavaScript 编程语言中回调函数的概念和用法。

函数是对象

我们需要知道的第一件事是,在 JavaScript 中,函数是第一类对象。因此,我们可以用处理其他对象的方式来处理它们,比如把它们分配给变量,并把它们作为参数传给其他函数。这一点很重要,因为正是第二个方式使我们能够在应用程序中扩展功能。

回调函数

回调函数是一个被作为参数传递给另一个函数的函数,以便在稍后时间被“回调”。一个接受其他函数作为参数的函数被称为高阶函数,它包含回调函数何时被执行的逻辑。正是这两者的结合,使我们能够扩展应用的功能。

为了说明回调,让我们从一个简单的例子开始。

function createQuote(quote, callback){ 
  var myQuote = "Like I always say, " + quote;
  callback(myQuote); // 2
}

function logQuote(quote){
  console.log(quote);
}

createQuote("eat your vegetables!", logQuote); // 1

// 控制台的结果: 
// Like I always say, eat your vegetables!

在上面的例子中,createQuote 是高阶函数,它接受两个参数,第二个参数是回调。logQuote 函数被用来作为我们的回调函数传入。当我们执行 createQuote 函数(1)时,注意到当我们把 logQuote 作为参数传入时,没有给它加上括号。这是因为我们并不想立即执行回调函数,只是想把函数定义传递给高阶函数,以便以后可以执行。

另外,我们需要确保,如果我们传入的回调函数期望有参数,那么在执行回调时要提供这些参数。在上面的例子中,这将是 callback(myQuote); 语句,因为我们知道 logQuote 期望传入一句引言。

此外,我们可以将匿名函数作为回调传入。下面对 createQuote 的调用将产生与上述例子相同的结果。

createQuote("eat your vegetables!", function(quote){ 
  console.log(quote); 
});

顺便说一下,你不必用“回调”这个词作为参数的名称,JavaScript 只需要知道这是一个正确的参数名称。基于上面的例子,下面的函数将以完全相同的方式表现出来。

function createQuote(quote, functionToCall) { 
  var myQuote = "Like I always say, " + quote;
  functionToCall(myQuote);
}

为什么使用回调函数

大多数时候,我们创建的程序和应用都是以同步方式运行的。换句话说,我们的一些操作只有在前面的操作完成后才会开始。通常情况下,当我们从其他来源(如外部 API)请求数据时,我们并不总是知道数据何时会被传递回来。在这种情况下,我们希望等待响应,但我们并不总是希望整个应用程序在获取数据时停顿下来。这些情况就是回调函数的用武之地。

让我们来看看一个模拟向服务器发出请求的例子。

function serverRequest(query, callback){
  setTimeout(function(){
    var response = query + "full!";
    callback(response);
  },5000);
}

function getResults(results){
  console.log("Response from the server: " + results);
}

serverRequest("The glass is half ", getResults);

// 延迟 5 秒后,在控制台中显示:
// 服务器的响应:The glass is half full!

在上面的例子中,我们向一个服务器发出了一个模拟请求。5 秒过后,响应被修改,然后我们的回调函数 getResults 被执行。要查看这个过程,你可以将上述代码复制/粘贴到你的浏览器的开发工具中并执行。

另外,如果你已经熟悉 setTimeout,那么你就一直在使用回调函数。传入上述例子的 setTimeout 函数调用中的匿名函数参数也是一个回调函数! 所以这个例子的原始回调实际上是由另一个回调执行的。如果可以的话,请注意不要嵌套太多的回调,因为这可能会导致所谓的“回调地狱”!正如它的名字所暗示的那样,处理这个问题比较麻烦。