深刻理解 JavaScript 回調函數

做者:Nilesh Sanyal

翻譯:瘋狂的技術宅javascript

原文:https://dzone.com/articles/ja...前端

未經容許嚴禁轉載java

JavaScript回調函數是成爲一名成功的 JavaScript 開發人員必需要了解的一個重要概念。可是我相信,在閱讀本文以後,你將可以克服之前使用回調方法遇到的全部障礙。git

在開始以前,首先要確保咱們對函數的理解是紮實的。程序員

快速回顧:JavaScript 函數

什麼是函數?

函數是在其中有一組代碼的邏輯構件,用來執行特定任務。實際上爲了易於調試和維護,函數容許以更有組織的方式去編寫代碼。函數還容許代碼重用。github

你只需定義一次函數,而後在須要時去調用它,而沒必要一次又一次地編寫相同的代碼。面試

聲明一個函數

如今,讓咱們看看如何在 javascript 中聲明一個函數。segmentfault

  1. 使用函數的構造函數: 在這種方法中,函數是在「函數」的構造函數的幫助下建立的。從技術上講,這種方法比使用函數表達式語法和函數聲明語句語法去聲明函數的方法效率要低。
  2. 使用函數表達式: 一般這種方法與變量分配相同。簡而言之,函數主體被視爲一個表達式,而且該表達式被分配給一個變量。使用這種語法定義的函數能夠是命名函數或匿名函數。

沒有名稱的函數被稱爲匿名函數。匿名函數是自調用的,這意味着它會自動調用起自身。這種行爲也稱爲當即調用的函數表達式(IIFE)。api

  1. 使用函數聲明: 這種方法是 JavaScript 中經常使用的老派方法。在關鍵字「function」以後,你必須指定函數的名稱。以後,若是函數接受多個參數或參數,也須要說起它們。雖然這部分是徹底可選的。

    在函數體中,函數必須將一個值返回給調用方。遇到 return 語句後,該函數將會中止執行。在函數內部,參數將會充當局部變量。數組

    一樣,在函數內部聲明的變量是該函數的局部變量。局部變量只能在該函數內訪問,所以具備相同名稱的變量能夠輕鬆地用於不一樣的函數。

調用一個函數

在下列任何一種狀況下,將調用以前聲明的函數:

  • 發生事件時,例如,用戶單擊按鈕,或者用戶從下拉列表中選擇某些選項等等。
  • 從 javascript 代碼中調用該函數時。
  • 該函數能夠自動調用,咱們已經在匿名函數表達式中進行了討論。

    () 運算符調用該函數。

什麼是回調函數?

按照 MDN 的描述:回調函數是做爲參數傳給另外一個函數的函數,而後經過在外部函數內部調用該回調函數以完成某種操做

讓我用人話解釋一下,回調函數是一個函數,將會在另外一個函數完成執行後當即執行。回調函數是一個做爲參數傳給另外一個 JavaScript 函數的函數。這個回調函數會在傳給的函數內部執行。

在 JavaScript 中函數被看做是一類對象。對於一類對象,咱們的意思是指數字、函數或變量能夠與語言中的其餘實體相同。做爲一類對象,能夠將函數做爲變量傳給其餘函數,也能夠從其餘函數中返回這些函數。

能夠執行這種操做的函數被稱爲高階函數。回調函數其實是一種模式。 「模式」一詞表示解決軟件開發中常見問題的某種行之有效的方法。最好將回調函數做爲回調模式去使用。

爲何咱們須要回調

客戶端 JavaScript 在瀏覽器中運行,而且瀏覽器的主進程是單線程事件循環。若是咱們嘗試在單線程事件循環中執行長時間運行的操做,則會阻止該過程。從技術上講這是很差的,由於過程在等待操做完成時會中止處理其餘事件。

例如,alert 語句被視爲瀏覽器中 javascript 中的阻止代碼之一。若是運行 alert,則在關閉 alert 對話框窗口以前,你將沒法在瀏覽器中進行任何交互。爲了防止阻塞長時間運行的操做,咱們使用了回調。

讓咱們深刻研究一下,以便使你準確瞭解在哪一種狀況下使用回調。

image.png

在上面的代碼片斷中,首先執行 getMessage()函數,而後執行 displayMessage() 。二者都在瀏覽器的控制檯窗口中顯示了一條消息,而且都當即執行。

在某些狀況下,一些代碼不會當即執行。例如,若是咱們假設 getMessage() 函數執行 API 調用,則必須將請求發送到服務器並等待響應。這時咱們應該如何處理呢?

如何使用回調函數

我認爲與其告訴你 JavaScript 回調函數的語法,不如在前面的例子中實現回調函數更好。修改後的代碼段顯示在下面的截圖中。

image.png

爲了使用回調函數,咱們須要執行某種沒法當即顯示結果的任務。爲了模擬這種行爲,咱們用 JavaScript 的 setTimeout() 函數。該函數會暫停兩秒鐘,而後在控制檯窗口中顯示消息「 Hi,there」。

「顯示的消息」將被顯示在瀏覽器的控制檯窗口中。在這種狀況下,首先,咱們須要等待 getMessage() 函數。成功執行此函數後,再執行 displayMessage() 函數。

回調的工做方式

讓我解釋一下前面的例子在幕後發生的事。

從上一個例子能夠看到,在 getMessage() 函數中,咱們傳遞了兩個參數。第一個參數是 msg 變量,該變量顯示在瀏覽器的控制檯窗口中,第二個參數是回調函數。

如今,你可能想知道爲何將回調函數做爲參數進行傳遞 —— 要實現回調函數,咱們必須將一個函數做爲參數傳給另外一個函數。

getMessage() 完成任務後,咱們將調用回調函數。以後,當調用 getMessage() 函數時,將引用傳給displayMessage() 函數,該函數就是回調函數。

注意,當調用 getMessage() 函數時,咱們僅將其引用傳給 displayMessage() 函數。這就是爲何你不會在它旁邊看到函數調用運算符,也就是() 符號。

Javascript 回調是異步的嗎?

JavaScript 被認爲是單線程腳本語言。單線程是指 JavaScript 一次執行一個代碼塊。當 JavaScript 忙於執行一個塊時,它不可能移到下一個塊。

換句話說,咱們能夠認爲 JavaScript 代碼本質上老是阻塞的。可是這種阻塞性使咱們沒法在某些狀況下編寫代碼,由於在這些狀況下咱們沒有辦法在執行某些特定任務後當即獲得結果。

我談論的任務包括如下狀況:

  • 經過對某些端點進行 API 調用來獲取數據。
  • 經過發送網絡請求從遠程服務器獲取一些資源(例如,文本文件、圖像文件、二進制文件等)。

爲了處理這些狀況,必須編寫異步代碼,而回調函數是處理這些狀況的一種方法。因此從本質上上說,回調函數是異步的。

Javascript 回調地獄

當多個異步函數一個接一個地執行時,會產生回調地獄。它也被稱爲厄運金字塔。

假設你要獲取全部 Github 用戶的列表。而後在用戶中搜索 JavaScript 庫的主要貢獻者。再而後,你想要在用戶中獲取姓名爲 John 的人員的詳細信息。

爲了在回調的幫助下實現這個功能,代碼應該以下所示:

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

從上面的代碼片斷中,你能夠看到代碼變得更加難以理解,以及難以維護和修改。這是由回調函數的嵌套而引起的。

如何避免回調地獄?

可使用多種技術來避免回調地獄,以下所示。

  1. 使用promise
  2. 藉助 async-await
  3. 使用 async.js 庫

使用 Async.js 庫

讓咱們談談怎樣用 async.js 庫避免回調地獄。

根據 async.js 官方網站的描述:Async 是一個工具模塊,它提供了直接、強大的函數來使用異步 JavaScript

Async.js 總共提供約 70 個函數。如今,咱們將僅討論其中兩個,即 async.waterfall()async.series()

async.waterfall()

當你要一個接一個地運行某些任務,而後將結果從上一個任務傳到下一個任務時,這個函數很是有用。它須要一個函數「任務」數組和一個最終的「回調」函數,它會在「任務」數組中全部的函數完成後,或者用錯誤對象調用「回調」以後被調用。

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 回調函數的全部疑問。若是你以爲這篇文章有幫助,請與他人分享。


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索