天天看點

js中的回調函數,你有想過嗎?

前言

前段時間騰訊三面(沒看清要求,好像那個崗也要了解後端知識比如Redis但是我不會,已挂),有一個前端知識把我問懵了:請講一下js中的回調函數,回調函數是什麼?

講真,一直在用回調但是卻壓根沒有想過,确實是我本身的不足。

正文

回調函數就是一個通過函數指針調用的函數。如果你把函數的指針作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數的時候,我們就說這是回調函數。

函數指針,也就是函數的位址,可以看做是指向函數的指針變量。

函數指針有兩個用途:調用函數和做(别的)函數的參數!

官方對回調函數的定義是:作為參數傳遞給另一個函數并在其父函數執行完成後執行的函數。

可以明确的是:回調函數不是由該函數的實作方直接調用,而是在特定的事件/條件下由另外一方調用的,用于對該事件/條件進行響應。

這裡不得不提到一個概念:回調隊列(也有叫“消息隊列”) —— js在運作時除了函數調用棧之外,還包含了一個待處理的回調隊列。其中都是已經有了運作結果的異步任務,每一個任務都會關聯一個回調函數。

回調隊列遵循FIFO(先進先出)的原則,在js代碼執行中會進行一些處理:

  • 運作時,會從最先進入隊列的任務開始,處理隊列中的任務;
  • 被處理的任務會被移出隊列,該任務的運作結果會作為輸入參數,并調用與之關聯的函數,此時會産生一個函數調用棧;
  • 函數會一直處理調用棧直到再次為空,然後 eventLoop 會去處理隊列中的下一個任務

說起回調,就不得不提起“回調地獄” —— 為了達到某種效果而不斷在函數中添加函數參數。回調地獄主要有兩個問題:

  1. 多層嵌套;
  2. 每種任務的處理結果都存在兩種可能性(成功或失敗),那麼需要在每種任務執行結束後分别處理這兩種可能性。

es6用 Promise 解決回調地獄,本質上就是為了解決上面這兩個問題。promise裡有三大亮點:

1、 回調函數延遲綁定:回調函數不是直接聲明的,而是通過後面的 then 方法的調用才傳入的:

let readFile=filename=>{
  return new Promise((resolve,reject)=>{
    fs.readFile(filename,(err,data)=>{
      if(err){
        reject(err);
      }else{
        resolve(data);
      }
    })
  })
}
readFile('mxc.json').then(data=>{
  return readFile('mxc2.json');
})      

2、 傳回值穿透:我們根據 then 中回調函數的傳入值建立不同類型的 Promise,然後把傳回的 Promise 穿透到外層,以供後續調用 —— 也就是promise的“鍊式調用”:

3、 錯誤冒泡:依靠 promise 穿透的特點,我們可以把前面産生的錯誤一直向後傳遞,直到末尾被 catch 接收,這樣就不需要頻繁地檢查錯誤了。也不會因為前面的錯誤阻塞一些代碼的執行:

readFile('mxc.json').then(data=>{
  return readFile('mxc2.json');
}).then(data=>{
  return readFile('mxc3.json');
}).then(data=>{
  return readFile('mxc4.json');
}).catch(err=>{
  // xxx
})      
是的,promise 也是基于回調的

promise如何保證順序執行?

上面也說了,promise采用了延遲挂載的方式。而且你不知道目前promise的狀态是pending、resolved還是rejected。那如果then注冊的一套回調中如果既有同步任務也有異步任務,怎麼保證他們按順序執行呢?

關于此,promise/A+ 規範中提到:

onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
Promise.prototype.then=function(onFulfilled,onRejected){
  return new Promise((resolve,reject)=>{
    let callback={onFulfilled,onRejected,resolve,reject};
    
    if(this.state==PENDING){
      // 萬一 promise 還在 pending 的時候就挂了 then 呢?
      this.callbacks.push(callback);
    }else{
      setTimeout(()=>handleCallback(callback,this.state,this.result),0);
    }
  })
}      

繼續閱讀