譯文:JS回調(diào)函數(shù)深度指南

我是法醫(yī),一只治療系前端碼猿??,與代碼對(duì)話,傾聽它們心底的呼聲,期待著大家的點(diǎn)贊??與關(guān)注?,當(dāng)然也歡迎加入前端獵手技術(shù)交流群??,文末掃碼我拉你進(jìn)群,一起交流技術(shù)以及代碼之外的一切???♀?
轉(zhuǎn)載自Duing(ID:duyi-duing)
原文鏈接:https://dev.to/nileshsanyal/javascript-callback-functions-in-depth-guide-for-2019-gj7
作者:Nilesh Sanyal
如有翻譯不準(zhǔn),請(qǐng)多指正。
JS回調(diào)函數(shù)是你必須了解的重要概念,否則,在成為一名成功的前端開發(fā)工程師可能會(huì)面臨很多障礙。但我相信,在徹底閱讀本文之后,您將能夠克服以前在回調(diào)方面遇到的任何困難。
本文我將分享關(guān)于回調(diào)函數(shù)的很多技巧。但首先,你需要對(duì)函數(shù)有一些基本的認(rèn)識(shí)。我的意思是你至少應(yīng)該知道什么是函數(shù),它是如何工作的,什么是不同類型的函數(shù)等等。
什么是函數(shù)?
函數(shù)是一個(gè)邏輯構(gòu)建塊,其中編寫了一組代碼以執(zhí)行特定任務(wù)。實(shí)際上,函數(shù)允許以更有條理的方式編寫代碼,這也易于調(diào)試和維護(hù)。函數(shù)還允許代碼重用。
函數(shù)只需定義一次,就可以在需要時(shí)調(diào)用它,而不必一次又一次地編寫相同的代碼。
我們討論了一下什么是函數(shù),現(xiàn)在,讓我們看看如何在JS中聲明函數(shù)。
使用函數(shù)構(gòu)造函數(shù):
在此方法中,函數(shù)是在"函數(shù)"構(gòu)造函數(shù)的幫助下創(chuàng)建的。從技術(shù)上講,這種方法不如使用函數(shù)表達(dá)式語法和函數(shù)聲明語句語法聲明函數(shù)更高效。

使用函數(shù)表達(dá)式:
通常,此方法與變量賦值相同。簡(jiǎn)單來說,函數(shù)體被視為表達(dá)式,該表達(dá)式分配給變量。使用此語法定義的函數(shù)可以是命名函數(shù),也可以是匿名函數(shù)。
沒有名稱的函數(shù)稱為匿名函數(shù)。匿名函數(shù)是自我調(diào)用的,這意味著它會(huì)自動(dòng)調(diào)用自己。此行為也稱為立即調(diào)用的函數(shù)表達(dá)式 (IIFE)。


使用函數(shù)聲明語句:
實(shí)際上,此方法是JS中常用的老方法。在這里,在關(guān)鍵字"函數(shù)"之后,您必須指定函數(shù)的名稱。之后,如果函數(shù)接受多個(gè)參數(shù)或參數(shù);你也必須要提到它們。盡管此部分是完全可選的。
在函數(shù)的主體中,函數(shù)必須向調(diào)用方反饋一個(gè)值。找到return語句后,該函數(shù)將停止執(zhí)行。在函數(shù)內(nèi)部,參數(shù)將充當(dāng)局部變量。
此外,在函數(shù)內(nèi)聲明的變量將是該函數(shù)的本地變量。只能在該函數(shù)中訪問局部變量,因此具有相同名稱的變量可以輕松地用于不同的函數(shù)。

發(fā)生以下任一情況時(shí),將調(diào)用之前被聲明的函數(shù):
例如,當(dāng)事件發(fā)生時(shí),用戶單擊按鈕或用戶從下拉列表中選擇某個(gè)選項(xiàng)等。
當(dāng)從JS代碼調(diào)用函數(shù)時(shí)。
該函數(shù)也可以自動(dòng)調(diào)用,這一點(diǎn)我們已經(jīng)在匿名函數(shù)表達(dá)式中討論過了。
()運(yùn)算符調(diào)用該函數(shù)。
根據(jù)MDN:回調(diào)函數(shù)是作為參數(shù)傳遞給另一個(gè)函數(shù)的函數(shù),然后在外部函數(shù)內(nèi)部調(diào)用該函數(shù)以完成某種例程或操作。
我知道在閱讀了這個(gè)技術(shù)定義之后,你會(huì)感到困惑,幾乎無法理解什么是真正的回調(diào)函數(shù)。
讓我用簡(jiǎn)單的詞來解釋一下,回調(diào)函數(shù)是一個(gè)函數(shù),它將在另一個(gè)函數(shù)完成執(zhí)行后立即執(zhí)行。回調(diào)函數(shù)是作為參數(shù)傳遞給另一個(gè)JS函數(shù)的函數(shù)。該回調(diào)函數(shù)在它所傳遞到的函數(shù)內(nèi)部執(zhí)行。
在JS中,函數(shù)被視為一等對(duì)象。通過說一等對(duì)象(我們的意思是一個(gè)數(shù)字或一個(gè)函數(shù)或一個(gè)變量)可以被視為與語言中的任何其他實(shí)體相同。作為一個(gè)一等對(duì)象,我們可以將函數(shù)傳遞給其他函數(shù),因?yàn)樽兞亢秃瘮?shù)也可以從其他函數(shù)返回。
可以執(zhí)行此操作的函數(shù)稱為高階函數(shù)。回調(diào)函數(shù)實(shí)際上是一種模式。"模式"這個(gè)詞意味著某種經(jīng)過驗(yàn)證的方法來解決軟件開發(fā)中的常見問題。因此,我們最好將回調(diào)函數(shù)的使用稱為回調(diào)模式。
客戶端JS在瀏覽器中運(yùn)行,主瀏覽器進(jìn)程是單個(gè)線程事件循環(huán)。如果我們嘗試在單線程事件循環(huán)中執(zhí)行長(zhǎng)時(shí)間運(yùn)行的操作,則該過程將被阻止。這在技術(shù)上是錯(cuò)誤的,因?yàn)樵撨M(jìn)程在等待操作完成時(shí)會(huì)停止處理其他事件。
例如,"alert"語句被視為瀏覽器中JS中的阻塞代碼之一。如果您運(yùn)行警報(bào);您無法再在瀏覽器中執(zhí)行任何交互,直到關(guān)閉警報(bào)對(duì)話框窗口。為了防止阻塞長(zhǎng)時(shí)間運(yùn)行的操作,使用回調(diào)。
讓我們深入探討,以便您確切地了解在哪個(gè)方案中使用了回調(diào)。

在上面的代碼片段中,getMessage()函數(shù)首先執(zhí)行,然后執(zhí)行displayMessage()。兩者都在瀏覽器的控制臺(tái)窗口中顯示一條消息,并且都立即執(zhí)行。
但在某些情況下,某些代碼不會(huì)被立即執(zhí)行。例如,如果我們假設(shè)getMessage()函數(shù)執(zhí)行API調(diào)用,我們必須將請(qǐng)求發(fā)送到服務(wù)器并等待響應(yīng),那么我們將如何處理它?
很簡(jiǎn)單,為了處理這種情況,我們需要在JS中使用回調(diào)函數(shù)。
我認(rèn)為與其告訴你JS回調(diào)函數(shù)的語法,嘗試在前面的示例中實(shí)現(xiàn)回調(diào)函數(shù)會(huì)更好。代碼段顯示在下面的屏幕截圖中。

為了使用回調(diào)函數(shù),我們需要執(zhí)行某種無法立即顯示結(jié)果的任務(wù)。為了模擬這種行為,我們使用JS的setTimeout()函數(shù)。該函數(shù)將需要2秒鐘才能在控制臺(tái)窗口中顯示消息"Hi,there"。
顯示此消息后,瀏覽器的控制臺(tái)窗口中將顯示"Displayedmessage"?。因此,在這種情況下,首先我們正在等待getMessage()函數(shù),在成功執(zhí)行此函數(shù)后,我們將執(zhí)行displayMessage()函數(shù)。
讓我解釋一下在前面的例子中,幕后實(shí)際發(fā)生了什么。
從前面的示例中可以看出,在getMessage()函數(shù)中,我們傳遞了兩個(gè)參數(shù);第一個(gè)參數(shù)是"msg"變量,它顯示在瀏覽器的控制臺(tái)窗口中,第二個(gè)參數(shù)是"回調(diào)"函數(shù)。
現(xiàn)在,您可能想知道為什么"回調(diào)"函數(shù)作為參數(shù)傳遞。這是因?yàn)橐獙?shí)現(xiàn)回調(diào)函數(shù),我們必須將一個(gè)函數(shù)作為參數(shù)傳遞給另一個(gè)函數(shù)。
在getMessage()函數(shù)完成它的任務(wù)后,我們調(diào)用該 "callback()"函數(shù)。之后,當(dāng)我們調(diào)用getMessage()函數(shù)時(shí),我們傳遞了對(duì)"displayMessage()"函數(shù)的引用,該函數(shù)被視為回調(diào)函數(shù)。
請(qǐng)注意,當(dāng)調(diào)用getMessage()函數(shù)時(shí),我們只是傳遞對(duì) "displayMessage"函數(shù)的引用。這就是為什么,你不會(huì)看到函數(shù)調(diào)用運(yùn)算符,即旁邊的"()"。
JS被認(rèn)為是一種單線程腳本語言。術(shù)語"單線程"意味著JS一次執(zhí)行一個(gè)代碼塊。當(dāng)JS忙于執(zhí)行一個(gè)塊時(shí),它不可能移動(dòng)到下一個(gè)塊。
換句話說,我們可以說JS代碼本質(zhì)上總是阻塞的。但是這種阻塞性質(zhì)會(huì)妨礙我們?cè)谀承┣闆r下編寫代碼,因?yàn)槲覀冊(cè)谶\(yùn)行某些特定任務(wù)后無法獲得即時(shí)結(jié)果。
我說的是諸如以下之的任務(wù)。
向某個(gè)終結(jié)點(diǎn)發(fā)送API調(diào)用以獲取數(shù)據(jù)。
發(fā)送網(wǎng)絡(luò)請(qǐng)求以從遠(yuǎn)程服務(wù)器獲取某些資源(例如,文本文件、圖像文件、二進(jìn)制文件等)。
為了處理這些情況,我們必須編寫異步代碼,回調(diào)函數(shù)是處理這些情況的一種方法。因此,回調(diào)函數(shù)本質(zhì)上是異步的。
當(dāng)多個(gè)異步函數(shù)一個(gè)接一個(gè)地執(zhí)行時(shí),就會(huì)發(fā)生回調(diào)地獄。它也被稱為厄運(yùn)金字塔。
假設(shè)您要獲取所有g(shù)ithub用戶的列表,然后在您要搜索的用戶中,僅搜索JS存儲(chǔ)庫(kù)的頂級(jí)貢獻(xiàn)者。然后,在這些人中,您希望獲得名為Jhon的人的詳細(xì)信息。
要在回調(diào)的幫助下實(shí)現(xiàn)此功能,代碼片段將如下所示。

從上面的代碼片段中,您可以看到代碼變得更難理解,更難維護(hù),也更難修改。這是由于所有回調(diào)函數(shù)的嵌套而發(fā)生的。
可以使用多種技術(shù)來避免回調(diào)地獄,如下所示。
通過使用promise。
在異步的幫助下等待。
通過使用異步.js庫(kù)。
我已經(jīng)分享過,如何使用承諾以及異步等待如何有助于避免回調(diào)地獄。
通過使用async.js庫(kù)
讓我們來談?wù)勈褂胊sync.js庫(kù)以避免回調(diào)地獄。
根據(jù)async的官方網(wǎng)站.js:Async是一個(gè)實(shí)用程序模塊,它為使用異步JS提供了直接,強(qiáng)大的功能。
Async.js總共提供了近70個(gè)函數(shù)。現(xiàn)在,我們將只討論其中兩個(gè),即async.waterfall()和async.series()。
async.waterfall()
當(dāng)您想要一個(gè)接一個(gè)地運(yùn)行某些任務(wù),然后將結(jié)果從上一個(gè)任務(wù)傳遞到下一個(gè)任務(wù)時(shí),它非常有用。它需要一個(gè)函數(shù)"任務(wù)"數(shù)組和一個(gè)最終的"回調(diào)"函數(shù),該函數(shù)在"任務(wù)"數(shù)組中的所有函數(shù)都已完成或使用錯(cuò)誤對(duì)象調(diào)用"回調(diào)"之后調(diào)用。

async.series()
當(dāng)您想要運(yùn)行函數(shù),然后在所有函數(shù)成功執(zhí)行后需要獲取結(jié)果時(shí),此函數(shù)非常有用。async.waterfall()和 async.series()之間的主要區(qū)別在于 async.series()不會(huì)將數(shù)據(jù)從一個(gè)函數(shù)傳遞到另一個(gè)函數(shù)。

閉包
用技術(shù)術(shù)語來說,閉包是將函數(shù)捆綁在一起并引用其周圍狀態(tài)的組合。
簡(jiǎn)單地說,閉包允許從內(nèi)部函數(shù)訪問外部函數(shù)的作用域。
要使用閉包,我們需要在另一個(gè)函數(shù)中定義一個(gè)函數(shù)。然后我們需要返回它或?qū)⑵鋫鬟f給另一個(gè)函數(shù)。
回調(diào)
從概念上講,回調(diào)類似于閉包。回調(diào)基本上是一個(gè)函數(shù)接受另一個(gè)函數(shù)作為參數(shù)的地方。
RECOMMEND
? ?
很感謝小伙伴看到最后??,如果您覺得這篇文章有幫助到您的的話不妨關(guān)注?+點(diǎn)贊??+收藏??+評(píng)論??,您的支持就是我更新的最大動(dòng)力。
歡迎加入前端獵手技術(shù)交流群??,文末掃碼加我微信,我拉你進(jìn)群,一起交流技術(shù)以及代碼之外的一切???♀?
