繁体   English   中英

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])
      }
  }
 }

我该怎么做?

基础知识(不是关于回调,而是关于编程语言)

要了解回调,您必须先了解函数。 要理解 JavaScript 中的函数,您首先必须理解变量、值和函数。

几乎所有的编程语言都可以处理值。 所以如果你做过任何编程,你就会对什么是值有一个基本的了解(我将在这里大大简化值的类型,并将值和引用/指针都称为“值”)。

价值是一种东西。 例如一个数字或一个字符串。 所以22.31是一个值, "Hello Dave"是一个值。

大多数语言也有变量的概念(但并非所有语言都有)。 变量是我们赋予值的“名称”,以便更轻松地处理值。 例如,在下面, x是一个变量:

var x = 12;

.. 它的值为 12。

变量允许我们做什么? 它允许我们在计算中用一个值代替一个名称。 就像数学一样。 例如,如果x12并且我们知道我们可以将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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM