|
|
|
|
|
JavaScript 回調函數(shù),是將函數(shù)作為參數(shù)傳遞給另一個函數(shù),然后可以在另一個函數(shù)中調用該函數(shù)?;卣{有很多好處,它們開辟了很多編程可能性?;卣{方式也不是唯一的,我們可以同步回調,也可以異步回調,這就是我今天要說的內容。
同步回調
許多人第一次接觸回調是在他們了解到可以為同一個排序算法提供不同的比較函數(shù)時。例如,當使用Array.prototype.sort()
方法對整數(shù)數(shù)組進行排序時,可選參數(shù)是比較函數(shù)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 是比較函數(shù)
這種回調在大多數(shù)編程語言中都有,達到多態(tài)算法的效果。
上面說明了同步回調是如何工作的。同步回調在使用它們的高階函數(shù)內部執(zhí)行。當高階函數(shù)完成執(zhí)行時,其回調參數(shù)的執(zhí)行也完成。由于高階函數(shù)必須等待同步回調執(zhí)行完成,所以同步回調也稱為阻塞回調——回調的執(zhí)行會阻塞調用者函數(shù)的執(zhí)行。
JavaScript 中同步回調的一些其他示例是用于迭代數(shù)組的方法: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 ]
異步回調
如果說同步回調是實現(xiàn)更大編程靈活性的方法,那么異步回調是實現(xiàn)更高性能和用戶體驗的方法。
異步回調的強大之處在于 JavaScript 獨特的運行時模型。JavaScript 是一種單線程語言,也就是說 JavaScript 的執(zhí)行引擎只有一個調用棧。
神奇之處在于 JavaScript 運行時環(huán)境的 API 處理程序。對于 Web 瀏覽器,API 是 Web API;對于 Node.js,API 是 I/O API。執(zhí)行異步回調的任務被放入回調隊列。
在調用堆棧中的現(xiàn)有代碼運行完成后,事件循環(huán)(作為 JavaScript 引擎的一部分的進程)將回調隊列中的回調帶入執(zhí)行。
一旦執(zhí)行引擎運行回調,它會在下一個回調開始運行之前再次運行到完成(直到調用堆棧為空)。調用堆棧上的代碼的這種運行到完成一直持續(xù)到隊列中的所有回調都被執(zhí)行為止。
異步方面來自這樣一個事實,即回調不是在高階函數(shù)中立即執(zhí)行,而是放在回調隊列中等待輪到它在調用堆棧上運行。
高階函數(shù)是派發(fā)回調任務的函數(shù),而不是運行它的函數(shù)。
使用異步回調最普遍的例子是使用setTimeOut
方法。
console.log("setTimeout 之前")
setTimeout(
()=>{ console.log("這里是2秒后的結果") },
2000
)
console.log("setTimeout 之后")
第一個參數(shù)是回調函數(shù),第二個參數(shù)是等待的時間,以毫秒為單位。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
該程序從頂部開始,并在到達底部時順序執(zhí)行每一行。
我們可以把上面的例子改為異步回調。
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
,我們將回調函數(shù)的執(zhí)行推遲到稍后的時間點?;卣{函數(shù)只有在程序從上到下執(zhí)行完代碼后才會運行(即使延遲為0ms)。
同步回調和異步回調之間的主要區(qū)別在于同步回調立即執(zhí)行,而異步回調的執(zhí)行推遲到稍后的時間點。
如何判斷回調是同步還是異步?
回調是同步執(zhí)行還是異步執(zhí)行取決于調用它的函數(shù)。如果函數(shù)是異步的,那么回調也是異步的。
異步函數(shù)通常是執(zhí)行網(wǎng)絡請求、等待 I/O 操作(如鼠標單擊)、與文件系統(tǒng)交互或向數(shù)據(jù)庫發(fā)送查詢的函數(shù)。這些函數(shù)的共同點是它們與當前程序之外的東西進行交互,并且你的應用程序一直等待直到響應返回。
相反,同步回調在程序的當前上下文中執(zhí)行,與外界沒有交互。你會在函數(shù)式編程中找到同步回調,例如,為集合中的每個項目調用回調(例如.filter()
、.map()
、.reduce()
等)。JavaScript 語言中的大多數(shù)原型方法都是同步的。
如果你不確定一個回調函數(shù)是同步執(zhí)行還是異步執(zhí)行,你可以在回調內部和之后添加console.log
語句,看看哪個先打印。
總結
本文介紹了JavaScript回調函數(shù):同步回調與異步回調。無論是同步回調還是異步回調,都有各自的好處,在使用時需根據(jù)具體情況而選擇采用何種編程方式。
相關文章