簡體   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