[譯] JavaScript:回撥是什麼鬼?

磊仔發表於2019-03-04

配合簡單的示例,用短短 6 分鐘學習和理解回撥的基本知識。

[譯] JavaScript:回撥是什麼鬼?

回撥  —— 題圖來自 unsplash

回撥是什麼?

簡單講: 回撥是指在另一個函式執行完成之後被呼叫的函式  ——  因此得名“回撥”。

稍複雜地講: 在 JavaScript 中,函式也是物件。因此,函式可以傳入函式作為引數,也可以被其他函式返回。這樣的函式稱為高階函式。被作為引數傳入的函式就叫做回撥函式

^ 這聽起來有點囉唆,讓我們來看一些例子來簡化一下。

為什麼我們需要回撥?

有一個非常重要的原因 —— JavaScript 是事件驅動的語言。這意味著,JavaScript 不會因為要等待一個響應而停止當前執行,而是在監聽其他事件時繼續執行。來看一個基本的例子:

function first(){
  console.log(1);
}

function second(){
  console.log(2);
}

first();
second();複製程式碼

正如你所料,first 函式首先被執行,隨後 second 被執行 —— 控制檯輸出下面內容:

// 1
// 2複製程式碼

一切都如此美好。

但如果函式 first 包含某種不能立即執行的程式碼會如何呢?例如我們必須傳送請求然後等待響應的 API 請求?為了模擬這種狀況,我們將使用 setTimeout,它是一個在一段時間之後呼叫函式的 JavaScript 函式。我們將函式延遲 500 毫秒來模擬一個 API 請求,新程式碼長這樣:

function first(){
// 模擬程式碼延遲
  setTimeout( function(){
console.log(1);
  }, 500 );
}

function second(){
  console.log(2);
}

first();
second();複製程式碼

現在理解 setTimeout() 是如何工作的並不重要,重要的是你看到了我們已經把 console.log(1); 移動到了 500 秒延遲函式內部。那麼現在呼叫函式會發生什麼呢?

first();
second();

// 2
// 1複製程式碼

即使我們首先呼叫了 first() 函式,我們記錄的輸出結果卻在 second() 函式之後。

這不是 JavaScript 沒有按照我們想要的順序執行函式的問題,而是 JavaScript 在繼續向下執行 second() 之前沒有等待 first() 響應的問題。

所以為什麼給你看這個?因為你不能一個接一個地呼叫函式並希望它們按照正確的順序執行。回撥正是確保一段程式碼執行完畢之後再執行另一段程式碼的方式。

建立一個回撥

好了,說了這麼多,讓我們建立一個回撥!

首先,開啟你的 Chrome 開發者工具(Windows: Ctrl + Shift + J)(Mac: Cmd + Option + J),在控制檯輸入下面的函式宣告:

function doHomework(subject) {
  alert(`Starting my ${subject} homework.`);
}複製程式碼

上面,我們已經建立了 doHomework 函式。我們的函式攜帶一個變數,是我們正在研究的課題。在控制檯輸入下面內容呼叫你的函式:

doHomework('math');

// Alerts: Starting my math homework.複製程式碼

現在把我們的回撥加進來,我們傳入 callback 作為 doHomework() 的最後一個引數。這個回撥函式是我們定義在接下來要呼叫的 doHomework() 函式的第二個引數。

function doHomework(subject**, callback**) {
  alert(`Starting my ${subject} homework.`);
**callback();**
}

doHomework('math'**, function() {
  alert('Finished my homework');
}**);複製程式碼

如你所見,如果你將上面的程式碼輸入控制檯,你將依次得到兩個警告:第一個是“starting homework”,接著是“finished homework”。

但是你的回撥函式並不總是必須定義在函式呼叫裡面,它們也可以定義在你程式碼中的其他位置,比如這樣:

function doHomework(subject, callback) {
  alert(`Starting my ${subject} homework.`);
  callback();
}

function alertFinished(){
  alert('Finished my homework');
}

**doHomework('math', alertFinished);**複製程式碼

這個例子的結果和之前的例子完全一致。如你所見,我們在 doHomework() 函式呼叫中傳入了 alertFinished 函式定義作為引數!

實際應用案例

上週我發表了一篇關於如何用 38 行程式碼構建一個 Twitter 機器人的文章。文中的程式碼可以實現的唯一原因就是我使用了 Twitters API。當你向一個 API 傳送請求,在你操作響應內容之前你必須等待這個響應。這是回撥在實際應用中的絕佳案例。請求長這樣:

T.get('search/tweets', params, function(err, data, response) {
  if(!err){
    // 這裡是施展魔法之處
  } else {
    console.log(err);
  }
})複製程式碼
  • T.get 僅僅意味著我們將要向 Twitter 傳送一個 get 請求
  • 這個請求中有三個引數:‘search/tweets’ 是請求的路徑,params 是搜尋引數,隨後的一個匿名函式是我們的回撥。

回撥在這裡很重要,因為在我們的程式碼繼續執行之前我們需要等待一個來自服務端的響應。我們並不知道 API 請求會成功還是會失敗,所以通過 get 向 search/tweets 傳送了請求引數以後,我們要等待。一旦 Twitter 響應,我們的回撥函式就被呼叫。Twitter 要麼傳送一個 err(error)物件,要麼傳送一個 response 物件返回給我們。在我們的回撥函式中我們可以使用 if() 語句來區分請求是否成功,然後相應地處理新資料。

你做到了

幹得漂亮!你現在(理想狀況下)已經理解了回撥是什麼,回撥如何工作。這只是回撥的冰山一角,記住學無止境啊!我每週都會更新一些文章/教程,如果你願意接收每週一次的推送,點選這裡輸入你的郵箱訂閱吧!


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃

相關文章