什么是回調(diào)函數(shù)
簡(jiǎn)單來(lái)說(shuō),回調(diào)函數(shù)就是:只有等另一個(gè)函數(shù)執(zhí)行完了才可以執(zhí)行的函數(shù)。
復(fù)雜點(diǎn)說(shuō),在JavaScript中,函數(shù)都是對(duì)象, 因此在函數(shù)中可以將函數(shù)作為參數(shù)傳遞,也可以作為返回值;這樣的函數(shù)也稱為高階函數(shù),任何一個(gè)將函數(shù)作為參數(shù)傳遞的函數(shù)都稱為回調(diào)函數(shù)。
話不多說(shuō),直接看例子吧。
為什么需要回調(diào)
最重要的一個(gè)原因就是JavaScript是事件驅(qū)動(dòng)語(yǔ)言,這意味著JavaScript在監(jiān)聽(tīng)其他事件時(shí)會(huì)持續(xù)執(zhí)行而不是在執(zhí)行前一直等待響應(yīng),看下面這個(gè)例子:
function first() {
console.log(1);
}
function second() {
console.log(2)
}
first();
second();
如你所料,first函數(shù)首先被執(zhí)行,second函數(shù)其次被執(zhí)行 ——打印結(jié)果如下:
// 1
// 2
現(xiàn)在看來(lái)沒(méi)什么毛病
但是,當(dāng)first函數(shù)包含了一些不能立馬被執(zhí)行的代碼時(shí)會(huì)怎樣呢?比如,當(dāng)我們發(fā)送了一個(gè)需要等待響應(yīng)的API請(qǐng)求時(shí)。我們將用setTimeout(一個(gè)在設(shè)置了指定時(shí)間之后調(diào)用函數(shù)的js內(nèi)置方法)來(lái)模擬這個(gè)操作,我們把函數(shù)延遲500ms執(zhí)行來(lái)模擬API請(qǐng)求,新代碼如下:
function first() {
setTimeout(function(){
console.log(1);
}, 500)
}
function second() {
console.log(2)
}
first();
second();
現(xiàn)在明不明白setTimeout的工作原理不重要,重要的是你看到我們把console.log(1)放到了500ms的延遲內(nèi),那現(xiàn)在調(diào)用函數(shù)會(huì)發(fā)生什么呢?
first();
second();
// 2
// 1
雖然我們先調(diào)用了first()函數(shù),但是其結(jié)果卻在second()函數(shù)之后打印出來(lái)。
并不是JavaScript沒(méi)有按照我們想要的順序執(zhí)行,而是Javascript沒(méi)有等到first函數(shù)響應(yīng)就已經(jīng)往下執(zhí)行second()函數(shù)了。
為什么看到的是這個(gè)結(jié)果呢?因?yàn)槟悴豢赡芤粋€(gè)一個(gè)調(diào)用函數(shù)且希望它們按序執(zhí)行,回調(diào)函數(shù)是一個(gè)能讓某些代碼在其他代碼執(zhí)行完成前不會(huì)被執(zhí)行的方法。
創(chuàng)建一個(gè)回調(diào)函數(shù)
話不多說(shuō),秀代碼!
首先,打開(kāi)chrome瀏覽器的console界面(Windows:ctrl + shift + j,Mac:cmd + option + j)并輸入下面的函數(shù)聲明:
function doHomework(subject) {
alert(`Starting my ${subject} homework`)
}
上面我們創(chuàng)建了一個(gè)doHomework函數(shù),這個(gè)函數(shù)需要傳入一個(gè)科目參數(shù),在控制臺(tái)輸入以下代碼調(diào)用該函數(shù):
doHomework('math'); // Alerts:Starting my math homework.
現(xiàn)在來(lái)添加一個(gè)回調(diào)——作為doHomework的最后一個(gè)參數(shù)我們用callback關(guān)鍵字來(lái)傳遞,然后在調(diào)用doHomework的第二個(gè)參數(shù)中定義回調(diào)函數(shù)的第二個(gè)參數(shù)。
function doHomework(subject, callback) {
alert(`Starting my ${subject} homework`);
callback();
}
doHomework('math', function() {
alert('Finished my homework');
});
如果你在控制臺(tái)輸入了上面那些代碼你會(huì)依次看到兩個(gè)警示框彈出,starting homework 在finished homework之前。
其實(shí)回調(diào)函數(shù)沒(méi)必要每次都在調(diào)用函數(shù)時(shí)聲明,可以 在其他地方聲明,比如:
function doHomework(subject, callback) {
alert(`Starting my ${subject} homework.`);
callback();
}
function alertFinished(){
alert('Finished my homework');
}
doHomework('math', alertFinished);
這個(gè)例子的結(jié)果和上面那個(gè)例子是一樣的,只是設(shè)置的不一樣,在調(diào)用doHomework時(shí)我們把a(bǔ)lertFinished函數(shù)名作為參數(shù)傳遞了。
真實(shí)案例
上周我發(fā)布了一篇名為Build a simple Twitter Bot in 38 lines of code的文章,那篇文章
中的代碼生效的原因就在于Twitters API,當(dāng)你請(qǐng)求API時(shí),在你操作響應(yīng)結(jié)果之前你必須等待響應(yīng)。這在真實(shí)場(chǎng)景中是一個(gè)很好的回調(diào)例子,下面是請(qǐng)求的代碼:
T.get('search/tweets', params, function(err, data, response) {
if(!err){
// 這是見(jiàn)證奇跡的地方
} else {
console.log(err);
}
})
- T.get意味著我們將要向Twitter發(fā)起一個(gè)get請(qǐng)求
- 這個(gè)請(qǐng)求有三個(gè)參數(shù):‘search/tweets’是我們要請(qǐng)求的路徑,params是查詢參數(shù),最后那個(gè)匿名函數(shù)就是我們的回調(diào)
回調(diào)在這里很重要因?yàn)槲覀冃枰却?wù)器的響應(yīng)才能繼續(xù)我們的代碼,我們也不知道API請(qǐng)求成功與否,所以在通過(guò)get請(qǐng)求向search/tweets 發(fā)送參數(shù)后只能等待,一旦Twitter 響應(yīng)了回調(diào)函數(shù)就會(huì)被調(diào)用,然后發(fā)送一個(gè)err對(duì)象或者response對(duì)象給我們。在回調(diào)函數(shù)內(nèi)我們可以用 if 語(yǔ)句判斷請(qǐng)求是否成功,從而操作新數(shù)據(jù)。
你可以的
很棒,你現(xiàn)在可以理解回調(diào)函數(shù)是什么以及它是如何運(yùn)行的了。這僅僅只是回調(diào)函數(shù)的冰山一角,還有很多需要學(xué)習(xí)的。