提问人:Darren 提问时间:3/15/2017 更新时间:3/21/2019 访问量:769
JavaScript:回调函数是如何工作的?
JavaScript: How does a callback function work?
问:
我真的是 JS 的新手,我在编写/理解回调函数时遇到了很多麻烦 例如,假设我有以下代码,但我不想
takeNumbersGreaterThan(5);
执行至
insertNumbers();
已完成
numbers = [];
greaterThan = [];
insertNumbers();
takeNumbersGreaterThan(5);
insertNumbers(){
for (var i = 0; i<11; i++)
{
numbers.push(i)
}
}
takeNumbersGreaterThan(number){
for (var m = 0; m<numbers.length; m++)
{
if (numbers[m] > number)
{
greaterThan.push(numbers[m])
}
}
}
我该怎么做?
答:
如果我理解正确,您想了解更多关于回调的信息,并且想要使用它们。让我也尝试帮助您使用您的代码。
如果你想在使用回调函数后立即执行,你可以做这样的事情:takeNumbersGreaterThan(5);
insertNumbers();
numbers = [];
greaterThan = [];
insertNumbers(takeNumbersGreaterThan, 5);
function insertNumbers(callbackFunction, callbackFunctionParam){
for (var i = 0; i<11; i++)
{
numbers.push(i)
}
callbackFunction(callbackFunctionParam);
}
function takeNumbersGreaterThan(number){
for (var m = 0; m<numbers.length; m++)
{
if (numbers[m] > number)
{
greaterThan.push(numbers[m])
}
}
}
但这只是一个简单的示例,说明如何在一些计算后调用回调函数。此代码可以改进。关键是,您可以将回调函数作为函数上的参数传递,然后稍后执行此回调函数。
你已经在那里了。您的代码几乎完全正确。 您只是缺少函数键声明。
下面的脚本显示了如何在 insertNumbers 之后运行 takeNumbersGreaterThan。在我的示例中,我还更改了函数符号,以便将数组作为参数传递,并避免一些常见的“错误”,称为闭包。
var numbers = [];
var greaterThan = [];
var insertNumbers = function(numbers) {
for (var i = 0; i<11; i++)
numbers.push(i)
}
var takeNumbersGreaterThan = function(number, numbers, greaterThan){
for (var m = 0; m<numbers.length; m++) {
if (numbers[m] > number)
greaterThan.push(numbers[m]);
}
}
// run insert numbers
insertNumbers(numbers);
// run take numbers greater than
takeNumbersGreaterThan(5, numbers, greaterThan);
// log
document.write(greaterThan);
您的代码不使用任何异步调用,因此您需要使用任何回调来处理执行。但是,如果您想知道如何去做,这就是方法。
numbers = [];
greaterThan = [];
function insertNumbers(callback){
for (var i = 0; i<11; i++)
{
numbers.push(i)
}
callback(); // now execute the callback funtion
}
function takeNumbersGreaterThan(number){
for (var m = 0; m<numbers.length; m++)
{
if (numbers[m] > number)
{
greaterThan.push(numbers[m]);
}
}
console.log(greaterThan);
}
insertNumbers(function() { // here we send a functions as argument to insertNumbers which will execute when callback() is called
takeNumbersGreaterThan(5);
});
insertNumbers
采用一个名为“callback”的参数。完成后,我们只需运行.在对 的初始调用中,我们传递一个函数作为参数,该函数将在完成(或被调用)后立即执行。insertNumbers
callback()
insertNumber
insertNumers
callback()
代码(大部分)是按顺序执行的。在你提供的代码中,计算机将按照你提供的顺序运行代码。首先,它创建一个新的数组对象并将其设置为 numbers 变量,然后创建一个新的数组对象并将其设置为 greaterThan 变量。 然后,它运行 insertNumbers 函数。现在,计算机所做的是跳转到您在 insertNumbers 中定义的代码并执行所有这些代码。然后,在完成此操作后,它将返回执行它所在的初始代码线程,该线程位于第 4 行。因此,现在它将跳转到 takeNumbersGreaterThan 代码。因此,从功能上讲,您不需要任何回调,因为您的代码不会执行任何需要任意时间的事情。
在解释之后,您会看到 takeNumbersGreaterThan 直到执行 insertNumbers 之后才会被执行。
唯一不按顺序执行代码的时间是当您开始执行多核/线程代码时。
当某些东西需要任意时间时,例如从磁盘读取数据或从网络请求数据时,会使用回调。
回调可以存在,因为用 javascript(和许多其他语言)定义的函数在代码中作为对象存在。如果不在函数名称后面加上括号,则实际上引用函数对象就像引用任何其他变量一样。因此,您可以在代码中将该函数对象传递给其他代码位。这就是此示例中发生的情况。
setTimeout(myCallback, 5000)
function myCallback(){
console.log("5 seconds have passed");
}
所以,正如你所看到的,我可以把我的函数交给另一个函数,在这种情况下,在另一个函数完成任务后使用。myCallback
setTimeout
基础知识(不是关于回调,而是关于编程语言)
要理解回调,首先必须了解函数。要理解 javascript 中的函数,您首先必须了解变量、值和函数。
几乎所有的编程语言都可以处理值。因此,如果您做过任何编程,您就会对值有一个基本的概念(我将在这里大大简化值的类型,并将值和引用/指针都称为“值”)。
价值就是事物。例如,数字或字符串。所以是一个值,也是一个值。22.31
"Hello Dave"
大多数语言也有变量的概念(但并非所有语言都有)。变量是我们给值起的“名称”,以便于处理值。例如,在下面的 是一个变量:x
var x = 12;
..它的值是 12。
变量允许我们做什么?它允许我们在计算中用值代替名称。就像数学一样。例如,如果 is 并且我们知道我们可以添加,我们也可以执行以下操作:x
12
1
12
x + 1
函数是值
在 javascript 中,函数是值。例如,在下文中,我们将一个函数分配给一个变量:
function a () {return "Hello"}
var y = a;
由于变量的作用是允许您用名称代替值,那么如果您可以使用语法调用该函数,则意味着您也可以使用变量执行此操作:a
a()
y
y(); // returns "Hello"
回调
如果函数是值,这也意味着您可以将函数作为参数传递给其他函数。例如,以下是您通常在另一个函数中调用函数的方式:
function a () {return "Hello"}
function b () {return a() + " World"}
b(); // returns "Hello World"
如果可以将函数作为变量传递,则意味着可以执行以下操作:
function a () {return "Hello"}
function b () {return "Bye"}
function c (functionVariable) {return functionVariable() + " World"}
c(a); // returns "Hello World"
c(b); // returns "Bye World"
如您所见。回调一点也不特别。它们只是因为在 javascript 中,函数遵循与其他值(如数字、数组和字符串)相同的规则。
回调并不意味着异步
从上面的示例中可以看出,对函数的两次调用都返回一个值。因此,即使该函数接受回调,它也不是异步的。因此,回调既可用于同步代码,也可用于异步代码。c
c
同步回调的一个很好的例子是方法。它对数组进行排序,但接受一个可选的回调,以便您定义如何排序(按字母、数字、按姓氏等)。Array.sort()
为什么异步代码需要回调
现在,忘记ajax或网络代码。让我们看一个场景,它使异步代码使用回调的原因更加明显。
例如,假设你有一个按钮。现在,当用户单击此按钮时,您希望发生某些事情。你是怎么做到的?
大多数人做的第一件事可能是这样的:
while (1) {
if (button.isClicked) {
doSomething();
}
}
还行。所以这是一个无限循环,只检查按钮是否被点击,不检查其他任何内容。那么你期望浏览器如何更新 UI 并跟踪鼠标呢?这就引出了人们尝试做的下一件事:
while (1) {
if (button.isClicked) {
doSomething();
}
else {
updateUI();
}
}
好的,太好了。但有两个问题。首先,如果有人要编写像 Google Charts 或 jQuery 这样的库,或者任何与 UI 相关的内容,他们要么编写自己的循环,要么您必须手动将他们的函数复制/粘贴到 while 循环中。这无法扩展。其次,更重要的是,这是低效的。while 循环将使用 100% 的 CPU 时间检查按钮。如果浏览器可以告诉我们何时单击按钮,那不是更好吗?while(1)...
幸运的是,在 javascript 中,函数只是值。您可以将函数传递给浏览器,并告诉它在有人单击按钮时执行您的函数:
button.addEventListener('click', doSomething);
注意:请注意将函数视为变量和调用函数之间的区别。如果要将函数视为变量,只需使用名称即可。如果要调用函数,请使用大括号。
doSomething()
为什么大家都坚持要写异步函数
每个人似乎都坚持制作异步 API,尤其是在 javascript 等语言中,有两个原因。
首先,低级文件和网络 I/O 是异步的。这意味着,如果要与数据库或服务器通信或读取文件,则需要将其实现为异步。此外,javascript 是单线程的。因此,如果使用 I/O 函数的同步版本,则将冻结其他所有内容。
其次,事实证明,在很多情况下(但肯定不是全部)异步,单线程编程与同步多线程编程一样快,有时甚至更快。
综合起来,上述两个原因在js社区中造成了社会压力,要求确保所有的I/O代码都是异步的,以保持速度优势,不阻塞其他人的代码。
评论
function