回归基础:在JavaScript中回调函数是什么? — SitePoint

原文链接:https://www.sitepoint.com/callbacks-javascript/

当我们刚开始学习JavaScript的时候,很快就会碰到回调函数。这对初学者来说,回调函数既陌生又神秘。然而掌握它的原理,是掌握(JavaScript)这门语言的关键之一。在这篇文章中,通过简单易懂的例子,希望能让你掌握回调函数的基础。

最基础的问题: 在JavaScript中,什么是回调函数?

电话(译者注:这个真是一语相关,原文是Callbacks,指回拨的电话号码之类,也是回调的一种) — 图片由unsplash提供

什么是回调?

简单地说: 一个回调函数,就是在另外一个函数(通常是异步的)执行完之后再执行的函数,因而被命名为——回调。

更进一步地说: 在JavaScript中,函数是对象。正因如此,一个函数可以被其他函数作为参数(传入),也能被其他函数作为返回值返回。这种函数(译者注:起码要满足如下条件之一:1.接受一个或多个函数作为参数,2.将一个函数作为返回值返回)被称为高阶函数。任何函数,只要它作为参数传入且随后被调用,都可称之为回调函数。

看起来很复杂的样子,但别急,让我们往下看一些例子。

这篇文章首发于codeburst.io且获得了作者授权转载。如果你喜欢阅读, 为什么不去看看Brandon的其他文章呢?如果你想提高你的JavaScript技巧,可以前往SitePoint Premium参加我们的JavaScript入门课程。

为什么需要回调函数?

这是由于一个十分重要的原因 —— JavaScript是一门事件驱动的语言。这就意味着,JavaScript会持续监听事件且一直往下执行,而不会在事件响应前一直挂起。让我们一起看看下面这个简单的例子:

1
2
3
4
5
6
7
8
9
10
function first(){
console.log(1);
}

function second(){
console.log(2);
}

first();
second();

正如你所料,函数first会首先执行,函数second会在之后执行——控制台会打印如下结果:

1
2
// 1
// 2

看起来是符合我们的预期。

但如果函数first包含有一些不会立即执行的代码呢?例如,我们需要发送一个请求并等待它的响应呢?我们使用setTimeout去模拟这种场景,setTimeout是原生JavaScript方法,允许你在特定时间后调用一个函数。我们通过延迟500毫秒后再执行函数来模拟发送请求。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
function first(){
// Simulate a code delay
setTimeout( function(){
console.log(1);
}, 500 );
}

function second(){
console.log(2);
}

first();
second();

你现在不太需要立即搞懂setTimeout()的原理(如果你很好奇,我们有这主题相关的文章)。(现在)最重要的是我们将console.log(1); 延迟500毫秒执行。那么,当我们执行这两个函数(firstsecond)时,会发生什么呢?

1
2
3
4
first();
second();
// 2
// 1

尽管我们先调用函数first,但会先打印出2

这并不是JavaScript不按照我们的想法去顺序执行函数,而是JavaScript会继续执行函数second,而不是等待函数fitst执行完(再去执行函数second)。

那么为何让你看这个(例子)呢?是因为你不能期望Javascript都是执行完一个函数后再去执行下一个函数。而回调函数是确保代码按次序正确执行的一种方法。

创造一个回调函数

好了,让我们少啰嗦,一起来创造一个回调函数吧!

首先,打开你浏览器的控制台(Windows/Linux和系统可以按下Ctrl + Shift + J,Mac则按下Cmd + Option + J)。然后把以下的函数输入到控制台内:

1
2
3
function doHomework(subject) {
alert(`Starting my ${subject} homework.`);
}

我们创建了一个名为doHomework的函数。而我们的函数需要传入一个变量才能工作。可以往控制台中输入以下代码调用(该函数)。

1
2
doHomework('math');
// Alerts: Starting my math homework.

现在我们把回调函数添加进去吧。doHomework需要添加多一个参数,回调函数会作为最后一个参数传入,并在执行doHomework时调用。

1
2
3
4
5
6
7
8
function doHomework(subject, callback) {
alert(`Starting my ${subject} homework.`);
callback();
}

doHomework('math', function() {
alert('Finished my homework');
});

试着在JS Bin上执行上述代码

JS Bin

正如你所看到的,当你在控制台输入上述代码后,陆续会有两个告示框弹出:首先是:‘Starting homework’,然后是‘Finished homework’。

然而回调函数不一定要在函数调用时完整传入。你可以将回调函数定义在其他地方,如以下代码所示:

1
2
3
4
5
6
7
8
9
10
function doHomework(subject, callback) {
alert(`Starting my ${subject} homework.`);
callback();
}

function alertFinished(){
alert('Finished my homework');
}

doHomework('math', alertFinished);

在JS Bin上执行上述例子

JavaScript中什么是回调函数?

执行的结果与之前的例子是完全一致的,但代码的组织却有一点不同。正如你所见,在doHomework函数调用时,alertFinished作为它的一个参数被传入!

一个真实的例子

上星期,我发布了一篇名为《如何用38行代码创造一个推特机器人》的文章。那篇文章中,代码是通过使用推特的API来工作的。当你发请求去一个API时,你必须等待响应内容返回后才能继续工作。这是一个说明如何使用回调函数的很好例子。请求API时发送的请求代码如下:

1
2
3
4
5
6
7
T.get('search/tweets', params, function(err, data, response) {
if(!err){
// This is where the magic will happen
} else {
console.log(err);
}
});
  • T.get 的意思只是往推特发送一个GET请求。

  • 当发送请求去‘search/tweets’时,(请求函数)接受三个参数,第一个参数是请求地址,params是第二个参数,代表搜索相关的参数,而最后一个则是一个匿名的回调函数。

在这里,回调函数是十分重要的,因为我们需要等待服务器返回数据后,才能继续执行余下代码。我们并不知道我们请求API后,服务器是否成功地返回数据。因此,当我们通过GET请求请求search/tweets后,会等待服务器返回信息。一旦推特(的服务器)返回相应数据,我们的回调函数将会执行。推特服务器会返回一个err (error) 对象或一个response对象给我们。在我们的回调函数中,我们使用条件判断语句if()(判断是否有错误对象存在)来确定请求是否成功,(判断成功)之后再根据返回的数据进行下一步操作。

初战告捷

干得漂亮!你现在应该初步理解回调函数是什么和它是如何工作的了,但这只是(回调函数知识中)的冰山一角,你仍有很多东西需要学习!如果你有任何问题或想法,请在评论中告诉我,我很乐意与你们分享和交流。