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