2019 年 JavaScript 回调函数深度指南
快速回顾:JavaScript 函数
什么是JavaScript回调函数?
为什么我们需要 JavaScript 回调?
如何使用JavaScript回调函数?
JavaScript回调函数的工作原理是什么?
JavaScript回调函数是异步的吗?
什么是JavaScript回调地狱?
如何才能摆脱回调地狱?
结语
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
点击此处阅读原文
JavaScript 回调函数是另一个你必须理解的重要概念。否则,你在成为一名成功的 JavaScript 开发者的道路上可能会遇到很多困难。但我相信,在认真阅读本文之后,你一定能够克服之前在回调函数方面遇到的所有障碍。
之后我会详细讲解回调函数,但首先,你需要对函数有一些基本的了解。我的意思是,你应该知道什么是函数,它是如何工作的,函数有哪些不同的类型等等。
快速回顾:JavaScript 函数
什么是函数?
函数是一种逻辑构建块,其中编写了一组代码以执行特定任务。实际上,函数使得代码编写更加系统化,也更易于调试和维护。此外,函数还支持代码重用。
你只需定义一次函数,需要时即可调用它,无需一遍又一遍地编写相同的代码。
声明函数的语法
我们简单讨论了一下函数是什么。现在,让我们看看如何在 JavaScript 中声明一个函数。
- 使用函数构造函数:在这种方法中,函数是借助“Function”构造函数创建的。从技术角度来看,这种方法不如使用函数表达式语法和函数声明语句语法来声明函数高效。
- 使用函数表达式:通常,这种方法与变量赋值相同。简单来说,函数体被视为一个表达式,并将该表达式赋值给一个变量。使用此语法定义的函数可以是具名函数,也可以是匿名函数。
没有名称的函数称为匿名函数。匿名函数是自调用的,也就是说它会自动调用自身。这种行为也称为立即调用函数表达式(IIFE)。
- 使用函数声明语句:实际上,这是 JavaScript 中常用的一种传统方法。在这种方法中,需要在关键字“function”之后指定函数名。此外,如果函数接受多个参数,也需要一并列出。不过,这部分是完全可选的。
在函数体中,函数必须向调用者返回一个值。找到 return 语句后,函数将停止执行。在函数内部,参数将作为局部变量。
此外,在函数内部声明的变量将是该函数的局部变量。局部变量只能在该函数内部访问,因此同名变量很容易在不同的函数中使用。
调用函数
当发生下列任一情况时,将调用之前声明的函数:
-
例如,当某个事件发生时,用户点击按钮或从下拉列表中选择某个选项等。
-
当函数从 JavaScript 代码中调用时。
-
该函数也可以自动调用,我们已经在匿名函数表达式中讨论过这一点。
() 运算符用于调用函数。
什么是JavaScript回调函数?
根据 MDN 的说法:回调函数是将一个函数作为参数传递给另一个函数,然后在外部函数内部调用该函数以完成某种例程或操作。
我知道读完这个技术定义后,你感到困惑,很难理解回调函数到底是什么。
让我用简单的话解释一下:回调函数是指在另一个函数执行完毕后立即执行的函数。回调函数可以作为参数传递给另一个 JavaScript 函数。该回调函数会在它被传递参数的函数内部执行。
在 JavaScript 中,函数被视为一等对象。所谓一等对象,是指数字、函数或变量等对象,可以像语言中的任何其他实体一样被对待。作为一等对象,我们可以将函数作为变量传递给其他函数,函数也可以从其他函数返回。
能够做到这一点的函数被称为高阶函数。回调函数实际上是一种模式。“模式”一词指的是一种经过验证的、用于解决软件开发中常见问题的方法。因此,将回调函数的使用称为回调模式更为恰当。
为什么我们需要 JavaScript 回调?
客户端 JavaScript 运行在浏览器中,而主浏览器进程是一个单线程事件循环。如果我们在单线程事件循环中尝试执行耗时较长的操作,进程就会被阻塞。这在技术上是不合理的,因为在等待操作完成期间,进程会停止处理其他事件。
例如,在浏览器中,“alert”语句被视为JavaScript中的阻塞代码之一。如果运行alert语句,您将无法在浏览器中进行任何操作,直到关闭alert对话框窗口。为了防止长时间运行的操作阻塞浏览器,通常会使用回调函数。
让我们深入了解一下,以便您能够准确地了解回调函数在哪些情况下使用。
在上面的代码片段中,getMessage() 函数首先执行,然后 displayMessage() 函数执行。两者都在浏览器的控制台窗口中显示了一条消息,并且都立即执行完毕。
但在某些情况下,一些代码不会立即执行。例如,假设getMessage()函数执行一个API调用,我们需要向服务器发送请求并等待响应,那么我们该如何处理这种情况呢?
很简单,要处理这种情况,我们需要在 JavaScript 中使用回调函数。
如何使用JavaScript回调函数?
与其讲解 JavaScript 回调函数的语法,不如我们尝试在之前的示例上实现回调函数。代码片段如下所示(见截图)。
为了使用回调函数,我们需要执行一些无法立即显示结果的任务。为了模拟这种行为,我们使用了 JavaScript 的 setTimeout() 函数。该函数会在 2 秒钟后在控制台窗口中显示消息“Hi, there”。
此消息显示完毕后,浏览器控制台窗口将显示“已显示消息”。因此,在这种情况下,我们首先等待 getMessage() 函数执行完毕,待该函数成功执行后,再执行 displayMessage() 函数。
JavaScript回调函数的工作原理是什么?
让我解释一下上一个例子背后实际发生了什么。
从前面的示例中可以看到,在 getMessage() 函数中,我们传递了两个参数;第一个参数是“msg”变量,它会显示在浏览器的控制台窗口中,第二个参数是“回调”函数。
现在,你可能会好奇为什么要把“回调”函数作为参数传递。这是因为要实现回调函数,我们必须将一个函数作为参数传递给另一个函数。
在 `getMessage()` 函数完成其任务后,我们会调用 `callback()` 函数。之后,当我们调用 `getMessage()` 函数时,我们会传递 `displayMessage()` 函数的引用,该函数被视为回调函数。
请注意,当调用 `getMessage()` 函数时,我们只是传递了对 `displayMessage` 函数的引用。因此,您不会在其旁边看到函数调用运算符,即 `()`。
JavaScript回调函数是异步的吗?
JavaScript 被认为是一种单线程脚本语言。所谓“单线程”,是指 JavaScript 一次只能执行一个代码块。当 JavaScript 正在执行某个代码块时,它无法跳转到下一个代码块。
换句话说,我们可以说 JavaScript 代码本质上总是阻塞式的。但这种阻塞特性,使得我们在某些情况下无法编写代码,因为我们无法在执行某些特定任务后立即获得结果。
我指的是诸如以下这类任务。
-
向特定端点发送 API 调用以获取数据。
-
向远程服务器发送网络请求以获取某些资源(例如,文本文件、图像文件、二进制文件等)。
为了处理这些情况,我们必须编写异步代码,而回调函数是处理这些情况的一种方法。因此,回调函数本质上是异步的。
什么是JavaScript回调地狱?
回调地狱是指多个异步函数依次执行的陷阱,也称为“厄运金字塔”。
假设你想获取所有 GitHub 用户列表,然后在这些用户中筛选出 JavaScript 代码库的主要贡献者。最后,你想从这些贡献者中获取名为 Jhon 的用户的详细信息。
要借助回调函数实现此功能,代码片段将与下面所示的类似。
http.get('https://api.github.com/users', function(users) {
/* Display all users */
console.log(users);
http.get('https://api.github.com/repos/javascript/contributors?q=contributions&order=desc', function(contributors) {
/* Display all top contributors */
console.log(contributors);
http.get('https://api.github.com/users/Jhon', function(userData) {
/* Display user with username 'Jhon' */
console.log(userData);
});
});
});
从上面的代码片段可以看出,由于所有回调函数都嵌套在一起,代码变得越来越难以理解、维护和修改。
如何才能摆脱回调地狱?
避免回调地狱可以使用多种技术,具体如下。
-
通过使用承诺。
-
借助 async/await。
-
通过使用 async.js 库。
我已经讨论过如何使用 Promise 以及 async/await 如何帮助避免回调地狱。
通过使用 Async.js 库
让我们来谈谈如何使用 async.js 库来避免回调地狱。
根据 async.js 官方网站的介绍:Async 是一个实用模块,它提供了简单、强大的函数,用于处理异步 JavaScript。
Async.js 总共提供了近 70 个函数。现在,我们只讨论其中的两个,即async.waterfall()和async.series()。
异步.瀑布()
当您需要按顺序运行多个任务,并将前一个任务的结果传递给下一个任务时,此方法非常有用。它接受一个函数数组“tasks”和一个最终的“callback”函数。当“tasks”数组中的所有函数都执行完毕,或者当“callback”函数被调用并传入错误对象时,该回调函数将被调用。
var async = require('async');
async.waterfall([
function(callback) {
/*
Here, the first argument value is null, it indicates that
the next function will be executed from the array of functions.
If the value was true or any string then final callback function
will be executed, other remaining functions in the array
will not be executed.
*/
callback(null, 'one', 'two');
},
function(param1, param2, callback) {
// param1 now equals 'one' and param2 now equals 'two'
callback(null, 'three');
},
function(param1, callback) {
// param1 now equals 'three'
callback(null, 'done');
}
], function (err, result) {
/*
This is the final callback function.
result now equals 'done'
*/
});
async.series()
当您需要运行多个函数,并在所有函数成功执行后获取结果时,此函数非常有用。async.waterfall() 和 async.series() 的主要区别在于,async.series() 不会将数据从一个函数传递到另一个函数。
async.series([
function(callback) {
// do some stuff ...
callback(null, 'one');
},
function(callback) {
// do some more stuff ...
callback(null, 'two');
}
],
// optional callback
function(err, results) {
// results is now equal to ['one', 'two']
});
JavaScript 回调函数与闭包
关闭
从技术角度来说,闭包是将多个函数捆绑在一起,并引用其周围状态的组合。
简单来说,闭包允许从内部函数访问外部函数的作用域。
要使用闭包,我们需要在一个函数内部定义一个函数。然后我们需要返回这个闭包或者将它传递给另一个函数。
打回来
从概念上讲,回调函数与闭包类似。回调函数本质上是指一个函数接受另一个函数作为参数。
结语
希望这篇文章能解答你对 JavaScript 回调函数的所有疑问。如果你觉得这篇文章有用,请分享给其他人。
文章来源:https://dev.to/nileshsanyal/javascript-callback-functions-in-deep-guide-for-2019-gj7






