|
|
|
|
|
|
JavaScript 回調函數,是將函數作為參數傳遞給另一個函數,然后可以在另一個函數中調用該函數。回調有很多好處,它們開辟了很多編程可能性。回調方式也不是唯一的,我們可以同步回調,也可以異步回調,這就是我今天要說的內容。
同步回調
許多人第一次接觸回調是在他們了解到可以為同一個排序算法提供不同的比較函數時。例如,當使用Array.prototype.sort()方法對整數數組進行排序時,可選參數是比較函數compareFn。
let arr1 = [22, 25, 55, 66, 23, 15, 1, 12]
arr1.sort() // arr1按升序排序
// arr1 變成 [1, 12, 15, 22, 23, 25, 55, 66]
let arr2 = […arr1] // 復制 arr1 到 arr2
arr2.sort((e1, e2)=> e2-e1) // arr2 按降序排序
// arr2 變成 [66, 55, 25, 23, 22, 15, 12, 1 ]
// (e1, e2)=> e2-e1 是比較函數
這種回調在大多數編程語言中都有,達到多態算法的效果。
上面說明了同步回調是如何工作的。同步回調在使用它們的高階函數內部執行。當高階函數完成執行時,其回調參數的執行也完成。由于高階函數必須等待同步回調執行完成,所以同步回調也稱為阻塞回調——回調的執行會阻塞調用者函數的執行。
JavaScript 中同步回調的一些其他示例是用于迭代數組的方法:forEach、map、filter、reduce、some、every等。
let arr = [1,2,3,4,5]
let arrDoubled = arr.map(e=>e+e)
console.log(arrDoubled) // 輸出 [ 2, 4, 6, 8, 10 ]
異步回調
如果說同步回調是實現更大編程靈活性的方法,那么異步回調是實現更高性能和用戶體驗的方法。
異步回調的強大之處在于 JavaScript 獨特的運行時模型。JavaScript 是一種單線程語言,也就是說 JavaScript 的執行引擎只有一個調用棧。
神奇之處在于 JavaScript 運行時環境的 API 處理程序。對于 Web 瀏覽器,API 是 Web API;對于 Node.js,API 是 I/O API。執行異步回調的任務被放入回調隊列。
在調用堆棧中的現有代碼運行完成后,事件循環(作為 JavaScript 引擎的一部分的進程)將回調隊列中的回調帶入執行。
一旦執行引擎運行回調,它會在下一個回調開始運行之前再次運行到完成(直到調用堆棧為空)。調用堆棧上的代碼的這種運行到完成一直持續到隊列中的所有回調都被執行為止。
異步方面來自這樣一個事實,即回調不是在高階函數中立即執行,而是放在回調隊列中等待輪到它在調用堆棧上運行。
高階函數是派發回調任務的函數,而不是運行它的函數。
使用異步回調最普遍的例子是使用setTimeOut方法。
console.log("setTimeout 之前")
setTimeout(
()=>{ console.log("這里是2秒后的結果") },
2000
)
console.log("setTimeout 之后")第一個參數是回調函數,第二個參數是等待的時間,以毫秒為單位。setTimeout無需等待回調完成即可返回。
以下是輸出的樣子:
setTimeout 之前
setTimeout 之后
這里是2秒后的結果
異步回調機制的好處是所有同步代碼都不會被異步事件阻塞。異步事件(例如對遠程服務器的 AJAX 請求)可能需要一些時間才能運行。通過異步回調,Web 應用程序可以更流暢地運行且響應速度更快。
示例:同步回調轉換為異步回調
console.log('start');
function getGreeting(name, cb) {
cb(`Hello ${name}`);
}
console.log('before getGreeting');
getGreeting('WebKaka', (greeting) => {
console.log(greeting);
});
console.log('end');輸出
start
before getGreeting
Hello WebKaka
end
該程序從頂部開始,并在到達底部時順序執行每一行。
我們可以把上面的例子改為異步回調。
console.log('start');
function getGreetingAsync(name, cb) {
setTimeout(() => {
cb(`Hello ${name}`);
}, 0);
}
console.log('before getGreetingAsync');
getGreetingAsync('WebKaka', (greeting) => {
console.log(greeting);
});
console.log('end');輸出
start
before getGreetingAsync
end
Hello WebKaka
通過添加 setTimeout,我們將回調函數的執行推遲到稍后的時間點。回調函數只有在程序從上到下執行完代碼后才會運行(即使延遲為0ms)。
同步回調和異步回調之間的主要區別在于同步回調立即執行,而異步回調的執行推遲到稍后的時間點。
如何判斷回調是同步還是異步?
回調是同步執行還是異步執行取決于調用它的函數。如果函數是異步的,那么回調也是異步的。
異步函數通常是執行網絡請求、等待 I/O 操作(如鼠標單擊)、與文件系統交互或向數據庫發送查詢的函數。這些函數的共同點是它們與當前程序之外的東西進行交互,并且你的應用程序一直等待直到響應返回。
相反,同步回調在程序的當前上下文中執行,與外界沒有交互。你會在函數式編程中找到同步回調,例如,為集合中的每個項目調用回調(例如.filter()、.map()、.reduce()等)。JavaScript 語言中的大多數原型方法都是同步的。
如果你不確定一個回調函數是同步執行還是異步執行,你可以在回調內部和之后添加console.log語句,看看哪個先打印。
總結
本文介紹了JavaScript回調函數:同步回調與異步回調。無論是同步回調還是異步回調,都有各自的好處,在使用時需根據具體情況而選擇采用何種編程方式。
相關文章
