JavaScript 回调函数是成为一名成功的 JavaScript 开发人员必须要了解的一个重要概念。但是我相信,在阅读本文之后,你将能够克服以前使用回调方法遇到的所有障碍。
在开始之前,首先要确保我们对函数的理解是扎实的。
函数是在其中有一组代码的逻辑构件,用来执行特定任务。实际上为了易于调试和维护,函数允许以更有组织的方式去编写代码。函数还允许代码重用。
你只需定义一次函数,然后在需要时去调用它,而不必一次又一次地编写相同的代码。
现在,让我们看看如何在 javascript 中声明一个函数。
在下列任何一种情况下,将调用之前声明的函数:
()
运算符调用该函数。按照 MDN 的描述:回调函数是作为参数传给另一个函数的函数,然后通过在外部函数内部调用该回调函数以完成某种操作。
让我用人话解释一下,回调函数是一个函数,将会在另一个函数完成执行后立即执行。回调函数是一个作为参数传给另一个 JavaScript 函数的函数。这个回调函数会在传给的函数内部执行。
在 JavaScript 中函数被看作是一类对象。对于一类对象,我们的意思是指数字、函数或变量可以与语言中的其他实体相同。作为一类对象,可以将函数作为变量传给其他函数,也可以从其他函数中返回这些函数。
可以执行这种操作的函数被称为高阶函数。回调函数实际上是一种模式。“模式”一词表示解决软件开发中常见问题的某种行之有效的方法。最好将回调函数作为回调模式去使用。
客户端 JavaScript 在浏览器中运行,并且浏览器的主进程是单线程事件循环。如果我们尝试在单线程事件循环中执行长时间运行的操作,则会阻止该过程。从技术上讲这是不好的,因为过程在等待操作完成时会停止处理其他事件。
例如,alert
语句被视为浏览器中 javascript 中的阻止代码之一。如果运行 alert,则在关闭 alert 对话框窗口之前,你将无法在浏览器中进行任何交互。为了防止阻塞长时间运行的操作,我们使用了回调。
让我们深入研究一下,以便使你准确了解在哪种情况下使用回调。
获取并显示消息的函数
在上面的代码片段中,首先执行 getMessage()
函数,然后执行 displayMessage()
。两者都在浏览器的控制台窗口中显示了一条消息,并且都立即执行。
在某些情况下,一些代码不会立即执行。例如,如果我们假设 getMessage()
函数执行 API 调用,则必须将请求发送到服务器并等待响应。这时我们应该如何处理呢?
我认为与其告诉你 JavaScript 回调函数的语法,不如在前面的例子中实现回调函数更好。修改后的代码段显示在下面的截图中。
用回调函数显示消息
为了使用回调函数,我们需要执行某种无法立即显示结果的任务。为了模拟这种行为,我们用 JavaScript 的 setTimeout()
函数。该函数会暂停两秒钟,然后在控制台窗口中显示消息“ Hi,there”。
“显示的消息”将被显示在浏览器的控制台窗口中。在这种情况下,首先,我们需要等待 getMessage()
函数。成功执行此函数后,再执行 displayMessage()
函数。
让我解释一下前面的例子在幕后发生的事。
从上一个例子可以看到,在 getMessage()
函数中,我们传递了两个参数。第一个参数是 msg
变量,该变量显示在浏览器的控制台窗口中,第二个参数是回调函数。
现在,你可能想知道为什么将回调函数作为参数进行传递 —— 要实现回调函数,我们必须将一个函数作为参数传给另一个函数。
在 getMessage()
完成任务后,我们将调用回调函数。之后,当调用 getMessage()
函数时,将引用传给displayMessage()
函数,该函数就是回调函数。
注意,当调用 getMessage()
函数时,我们仅将其引用传给 displayMessage()
函数。这就是为什么你不会在它旁边看到函数调用运算符,也就是()
符号。
JavaScript 被认为是单线程脚本语言。单线程是指 JavaScript 一次执行一个代码块。当 JavaScript 忙于执行一个块时,它不可能移到下一个块。
换句话说,我们可以认为 JavaScript 代码本质上总是阻塞的。但是这种阻塞性使我们无法在某些情况下编写代码,因为在这些情况下我们没有办法在执行某些特定任务后立即得到结果。
我谈论的任务包括以下情况:
为了处理这些情况,必须编写异步代码,而回调函数是处理这些情况的一种方法。所以从本质上上说,回调函数是异步的。
当多个异步函数一个接一个地执行时,会产生回调地狱。它也被称为厄运金字塔。
假设你要获取所有 Github 用户的列表。然后在用户中搜索 JavaScript 库的主要贡献者。再然后,你想要在用户中获取姓名为 John 的人员的详细信息。
为了在回调的帮助下实现这个功能,代码应该如下所示:
1http.get('https://api.github.com/users', function(users) {
2 /* Display all users */
3 console.log(users);
4 http.get('https://api.github.com/repos/javascript/contributors?q=contributions&order=desc', function(contributors) {
5 /* Display all top contributors */
6 console.log(contributors);
7 http.get('https://api.github.com/users/Jhon', function(userData) {
8 /* Display user with username 'Jhon' */
9 console.log(userData);
10 });
11 });
12});
从上面的代码片段中,你可以看到代码变得更加难以理解,以及难以维护和修改。这是由回调函数的嵌套而引发的。
可以使用多种技术来避免回调地狱,如下所示。
让我们谈谈怎样用 async.js 库避免回调地狱。
根据 async.js 官方网站的描述:Async 是一个工具模块,它提供了直接、强大的函数来使用异步 JavaScript。
Async.js 总共提供约 70 个函数。现在,我们将仅讨论其中两个,即 async.waterfall()
和 async.series()
。
当你要一个接一个地运行某些任务,然后将结果从上一个任务传到下一个任务时,这个函数非常有用。它需要一个函数“任务”数组和一个最终的“回调”函数,它会在“任务”数组中所有的函数完成后,或者用错误对象调用“回调”之后被调用。
1var async = require('async');
2async.waterfall([
3 function(callback) {
4 /*
5 Here, the first argument value is null, it indicates that
6 the next function will be executed from the array of functions.
7 If the value was true or any string then final callback function
8 will be executed, other remaining functions in the array
9 will not be executed.
10 */
11 callback(null, 'one', 'two');
12 },
13 function(param1, param2, callback) {
14 // param1 now equals 'one' and param2 now equals 'two'
15 callback(null, 'three');
16 },
17 function(param1, callback) {
18 // param1 now equals 'three'
19 callback(null, 'done');
20 }
21], function (err, result) {
22 /*
23 This is the final callback function.
24 result now equals 'done'
25 */
26});
当你要运行一个函数然后在所有函数成功执行后需要获取结果时,它很有用。 async.waterfall()
和 async.series()
之间的主要区别在于, async.series()
不会将数据从一个函数传递到另一个函数。
1async.series([
2 function(callback) {
3 // do some stuff ...
4 callback(null, 'one');
5 },
6 function(callback) {
7 // do some more stuff ...
8 callback(null, 'two');
9 }
10],
11// optional callback
12function(err, results) {
13 // results is now equal to ['one', 'two']
14});
用技术术语来说,闭包是捆绑在一起的函数的组合,引用了其周围的状态。
简而言之,闭包允许从内部函数访问外部函数的作用域。
要使用闭包,我们需要在一个函数内部定义另一个函数。然后,我们需要将其返回或传给另一个函数。
从概念上讲,回调类似于闭包。回调基本上是把一个函数作为另一个函数的用法。
希望本文能消除你对 javascript 回调函数的所有疑问。如果你觉得这篇文章有帮助,请与他人分享。