回呼函式 是作為引數傳遞給其他函式的函式。接著便可以呼叫這個回呼函式,觸發後續的動作。回呼函式經常用來處理工作完成的結果,或在事件發生時處理對應的動作。回呼函式可用於同步與非同步程式設計。
const sideLength = 5;
// Caller function takes a callback function
function applySideLength(callback) {
return callback(sideLength);
}
// Callback must expect the possible argument from the calling function
function areaOfSquare(side) {
return side * side;
}
applySideLength(areaOfSquare); // => 25
你也可以把回呼寫成函式運算式:
applySideLength(function squarePerimeter(side) {
return side * 4;
});
這在 JavaScript 中是很好用的模式,因為 JavaScript 的執行環境設計成單執行緒,同一時間只能執行一次函式呼叫。在執行期間,除非函式已經回傳,否則執行環境無法回應其他事件,也無法繼續執行。
許多 API 呼叫(I/O 函式、計時器與事件監聽器)會使用非同步機制,把目前的函式呼叫擱置在一旁,直到工作完成。完成之後,回呼函式會被放進訊息佇列,這樣當執行環境介於兩次函式呼叫之間時,就會藉由呼叫回呼函式來處理這些訊息。
使用回呼函式還有一個好處:它可以參照自己閉包作用域裡的變數,而這些變數是後來呼叫它的那個函式所無法存取的。
事件處理器是 JavaScript 中回呼的常見用途。像是 'onload' 或 'onclick' 這類瀏覽器事件,是能夠觸發函式被呼叫的訊號。DOM 的 [Document Object Model 物件,其 addEventListener 方法會註冊一個回呼函式,當它「聽到」事件發生時就會呼叫該函式。
document.addEventListener('onload', function () {
alert('The webpage has now been loaded');
});
在 Node.js 中,回呼函式遵循一套慣例來控制程式的流程。它們遵循這樣的模式:回呼函式的第一個引數可能會收到 Error 或 null;第二個之後的引數則會收到呼叫端函式所要傳送的資料。
如果發生錯誤,第二個之後的引數可能不存在,所以你不能依賴它們一定存在。
function operation(a, b, callback) {
// Work ...
if (/* an error occurs */) {
return callback(new Error('An error occurred'))
}
// On success:
callback(null, data)
}
function callback(error, returnedData) {
if (error) {
// An error occurred, handle it here.
return
}
// No error occurred, continue with the returned data.
}
operation(1, 2, callback)
在處理非同步函式、需要協助控制流程時,你很常見到這種模式。
常見的Array函式會用回呼函式來定義自己的行為:
Array.prototype.forEach:
接受一個回呼,並對陣列的每個元素套用這個回呼。
[1, 2, 3].forEach(function (element) {
doSomething(element);
});
// => doSomething() is invoked 3 times, once with each element
Array.prototype.map
接受一個回呼,對陣列的每個元素套用這個回呼,並用結果建立一個新陣列。
[1, 2, 3].map(function (element) {
return element + 1;
});
// => [2, 3, 4]
Array.prototype.reduce
接受一個回呼,對陣列的每個元素套用這個回呼,並把結果傳遞給下一次呼叫。
[1, 2, 3].reduce(function (runningSum, element) {
return runningSum + element;
}, 0);
// => 6