[英]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])
}
}
}
我该怎么做?
要了解回调,您必须先了解函数。 要理解 JavaScript 中的函数,您首先必须理解变量、值和函数。
几乎所有的编程语言都可以处理值。 所以如果你做过任何编程,你就会对什么是值有一个基本的了解(我将在这里大大简化值的类型,并将值和引用/指针都称为“值”)。
价值是一种东西。 例如一个数字或一个字符串。 所以22.31
是一个值, "Hello Dave"
是一个值。
大多数语言也有变量的概念(但并非所有语言都有)。 变量是我们赋予值的“名称”,以便更轻松地处理值。 例如,在下面, x
是一个变量:
var x = 12;
.. 它的值为 12。
变量允许我们做什么? 它允许我们在计算中用一个值代替一个名称。 就像数学一样。 例如,如果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(1)...
循环,要么您必须手动将它们的函数复制/粘贴到 while 循环中。 这没有规模。 其次,更重要的是,这是低效的。 该 while 循环将使用 100% CPU 时间检查按钮。 如果按钮被点击时浏览器可以告诉我们,那不是更好吗?
幸运的是,在 javascript 中,函数只是值。 您可以将一个函数传递给浏览器,并告诉它在有人单击按钮时执行您的函数:
button.addEventListener('click', doSomething);
注意:请注意将函数视为变量和调用函数之间的区别。 如果要将函数视为变量,只需使用名称即可。 如果你想调用一个函数,请使用像
doSomething()
这样的大括号。
每个人似乎都坚持制作异步 API 有两个原因,尤其是在像 javascript 这样的语言中。
首先,低级文件和网络 I/O 是异步的。 这意味着如果您想与数据库或服务器对话或读取文件,您需要将其实现为异步。 此外,javascript 是单线程的。 因此,如果您使用 I/O 函数的同步版本,您将冻结其他所有内容。
其次,事实证明,在很多情况下(但肯定不是全部)异步单线程编程与同步多线程编程一样快,有时甚至更快。
综合以上两个原因,在js社区造成了社会压力,需要确保所有的I/O代码都是异步的,以保持速度优势,而不是阻塞别人的代码。
如果我理解正确,您想了解更多有关回调的信息并想使用它们。 让我也尝试帮助您使用您的代码。
如果要执行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
接受一个名为“回调”的参数。 当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
,以便在另一个函数完成任务后使用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.