同期異步系列文章推薦
談一談javascript異步
javascript異步與promise
javascript異步之Promise.all()、Promise.race()、Promise.finally()
javascript異步之Promise.resolve()、Promise.reject()
javascript異步之Promise then和catch
javascript異步之async(一)
javascript異步之async(二)
javascript異步實戰
javascript異步總結歸檔javascript
咱們以前介紹了javascript異步的相關內容,咱們知道javascript以同步,單線程的方式執行主線程代碼,將異步內容放入事件隊列中,當主線程內容執行完畢就會當即循環事件隊列,直到事件隊列爲空,當用產生用戶交互事件(鼠標點擊,點擊鍵盤,滾動屏幕等待),會將事件插入事件隊列中,而後繼續執行。
處理異步邏輯最經常使用的方式是什麼?沒錯這就是咱們今天要說的---回調css
如你所知,函數是對象,因此能夠存儲在變量中,
因此函數還有如下身份:html
當一個函數a以一個函數做爲參數或者以一個函數做爲返回值時,那麼函數a就是高階函數
回調函數
百度百科java
回調函數就是一個經過函數指針調用的函數。若是你把函數的指針(地址)做爲參數傳遞給另外一個函數,當這個指針被用來調用其所指向的函數時,咱們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應。
維基百科ios
在計算機程序設計中,回調函數,或簡稱回調(Callback 即call then back 被主函數調用運算後會返回主函數),是指經過函數參數傳遞到其它代碼的,某一塊可執行代碼的引用。這一設計容許了底層代碼調用在高層定義的子程序。
回調函數,幾乎天天咱們都在用ajax
setTimeout(() => { console.log("這是回調函數"); }, 1000); const hero=['郭靖','黃蓉'] hero.forEach(item=>{ console.log(item); })
舉一個簡單的:axios
let girlName = "裘千尺" function hr() { girlName = "黃蓉" console.log(`我是${girlName}`); } function gj() { console.log(`${girlName}你好,我是郭靖,認識一下吧`); } hr() gj()
輸出,重點看輸出順序promise
//=>我是黃蓉 //=>黃蓉你好,我是郭靖,認識一下吧
上面的代碼輸出是沒什麼懸念的,不存在異步,都單線程同步執行,最後郭靖和黃蓉相識
若是這時候黃蓉很忙,出現了異步,會怎麼樣?異步
let girlName = "裘千尺" function hr() { setTimeout(() => { girlName = "黃蓉" console.log('我是黃蓉'); }, 0); } function gj() { console.log(`${girlName}你好,我是郭靖,認識一下吧`); } hr() gj()
輸出,重點看輸出順序async
//=>裘千尺你好,我是郭靖,認識一下吧 //=>我是黃蓉
雖然定時器是0ms,可是也致使了郭靖和黃蓉的擦肩而過,這不是咱們指望的結果,hr函數存在異步,只有等主線程的內容走完,才能走異步函數
因此最簡單的辦法就是使用回調函數解決這種問題,gj函數依賴於hr函數的執行結果,因此咱們把gj做爲hr的一個回調函數
let girlName = "裘千尺" function hr(callBack) { setTimeout(() => { girlName = "黃蓉" console.log('我是黃蓉'); callBack() }, 0); } function gj() { console.log(`${girlName}你好,我是郭靖,認識一下吧`); } hr(gj)
輸出,重點看輸出順序
//=>我是黃蓉 //=>黃蓉你好,我是郭靖,認識一下吧
⚠️:當回調函數做爲參數時,不要帶後面的括號!咱們只是傳遞函數的名稱,不是傳遞函數的執行結果
上面小栗子貌似的很簡單,咱們繼續
咱們把昨天的demo作一下升級
引入了lodash:處理按鈕點擊防抖
axios,集成了promis,但promise不是咱們今天討論的內容,咱們只使用axios的ajax請求接口功能
easy-mock:接口數據,用來實現ajax請求(數據是假的,可是請求是真的)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>javascript回調</title> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script src="https://cdn.bootcss.com/lodash.js/4.17.11/lodash.min.js"></script> </head> <body> <button>點擊</button> <script> { const btn = document.querySelector('button') btn.onclick = () => { _.debounce(() => { axios.get('https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/mock') .then(data => { console.log("ajax返回成功"); myData = data.data console.log(myData); }) .catch(error => { console.log("ajax返回失敗"); }) }, 500)() } } </script> </body> </html>
仔細看代碼,不難發現,這是一個典型的嵌套回調,咱們分析一下
第一層異步,用戶交互,來自按鈕的點擊事件
第二層異步,按鈕去抖,來自lodash下debounce的500ms延時
第三次異步,ajax請求,處理後臺接口數據
拿到數據後咱們沒有繼續作處理,在實際工做中可能還存在異步,還會繼續嵌套,會造成一個三角形的縮進區域
再繼續嵌套,就會造成所說的「回調地獄」,就是回調的層級太多了,代碼維護成本會高不少
上面的栗子最多算是入門毀掉地獄,咱們看一下這個
function funA(callBack) { console.log("A"); setTimeout(() => { callBack() }, 10); } function funB() { console.log("B"); } function funC(callBack) { console.log("C"); setTimeout(() => { callBack() }, 100); } function funD() { console.log("D"); } function funE() { console.log("E"); } function funF() { console.log("F"); } //從這裏開始執行 funA(() => { funB() funC(() => { funD() }) funE() }) funF()
(這段代碼,帶回調的都是異步邏輯)你能很快的看出這段代碼的執行順序嗎?
順序以下:A、F、B、C、E、D
通常正常人不會這麼嵌套多層,層級一多,就會考慮拆分
const btn = document.querySelector('button') //監聽按鈕點擊事件 btn.onclick = () => { debounceFun() } //去抖動 const debounceFun = _.debounce(() => { ajax() }, 500) //ajax 請求 const ajax = function () { axios.get('https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/mock') .then(data => { console.log("ajax返回成功"); myData = data.data console.log(myData); }) .catch(error => { console.log("ajax返回失敗"); }) }
我相信不少人都會經過這種鏈式回調的方式處理異步回調,由於可讀性比嵌套回調要搞,可是維護的成本可能要高不少
上面的栗子,三個異步函數之間只有執行順序上的關聯,並無數據上的關聯,可是實際開發中的狀況要比這個複雜,
咱們舉一個簡單的栗子
let girlName = "裘千尺" function hr(callBack) { setTimeout(() => { girlName = "黃蓉" console.log('我是黃蓉'); callBack(girlName) }, 0); } function gj(love) { console.log(`${girlName}你好,我是郭靖,認識一下吧,我喜歡${love}`); } hr(gj)
gj做爲hr的回調函數,而且hr將本身的一個變量傳遞給gj,gj在hr的回調中執行,
仔細看這種寫法並不嚴謹,
若是gj並不僅是一個function類型會怎麼樣?
若是love的實參並不存在會怎麼樣?
何況這只是一個簡單的栗子
因此回調函數中,參數的校驗是頗有必要的,回調函數鏈拉的越長,校驗的條件就會越多,代碼量就會越多,隨之而來的問題就是可讀性和可維護性就會下降。
但咱們引用了第三方的插件或庫的時候,有時候不免要出現異步回調的狀況,一個栗子:
xx支付,當用戶發起支付後,咱們將本身的一個回調函數,傳遞給xx支付,xx支付比較耗時,執行完以後,理論上它會去執行咱們傳遞給他的回調函數,是的理論上是這樣的,咱們把回調的執行權交給了第三方,隱患隨之而來
第三方支付,屢次調用咱們的回調函數怎麼辦?
第三方支付,不調用咱們的回調函數怎麼辦?
當咱們把回調函數的執行權交給別人時,咱們也要考慮各類場景可能會發生的問題
總結一下:
回調函數簡單方便,可是坑也很多,用的時候須要多注意校驗