基於ES6的Proxy,100行代碼實現一個XMLHttpRequest的攔截

前言

前一段時間,項目在對 WKWebview 進行適配時,接觸到了公共能力組使用的 Ajax-hook 方案,於是便對它的怎麼實現的很感興趣,到網上查資料學習時,找到了作者 @wendux 的 Ajax-hook原理解析 這篇文章,當時邊看腦子裡就邊想:“臥槽,這種騷操作怎麼感覺 Proxy 也能來一波!”。等看到這篇文章的評論區有個老哥 @銀冰雪千載 也發出了一樣的疑問時,會心一笑,說幹就幹,打開 VSCode 就是一頓操作。

基於ES6的Proxy,100行代碼實現一個XMLHttpRequest的攔截

關於 ES6 的 Proxy


這個東西其實也不新鮮了,不過由於不支持 IE ,且 Safari 10 才開始支持,用的時候一直小心翼翼的。一直在尋找一些最佳實踐,這次應該也算是一次練手。對它不太熟的同學可以看看 MDN上的Proxy 和 ECMAScript 6入門裡的Proxy 。此次實現,用到了它的get、set以及 construct 方法。

關於 XMLHttpRequest


XMLHttpRequest 我們並不陌生,雖然在諸如 axios 這類優秀的請求庫幫助下,我們漸漸不需要直接操作它了,但是對它的熟悉程度不應該停留在表層,在一些瀏覽器適配和前端監控以及埋點的時候,還是要和它打交道的。在這裡我們需要明確一些點:

  • 像 response、responseText、timeout這類的屬性,姑且稱之為 普通屬性
  • 對應的,像 onreadystatechange、onprogress、onload這類的屬性,則稱之為 事件屬性
  • 當然,還有一些 open、send、abort這類,稱之為
    方法
  • 這裡重點關注一個地方,有很大一部分屬性並不是 writable 的,如下圖
基於ES6的Proxy,100行代碼實現一個XMLHttpRequest的攔截

所以我們在攔截這些屬性時,要做些特殊處理

原理解析


這部分建議先看一下 API ,或者打開 API 放在旁邊對照著看,效果更佳。

和 Ajax-hook 一樣,總體是採用代理模式,下面上個總體的原理圖:

基於ES6的Proxy,100行代碼實現一個XMLHttpRequest的攔截

首先,無論項目(瀏覽器端)採用什麼請求方案,只要是最終用的是 built-in 對象 XMLHttpRequest ,都需要用

將其實例化,那麼我們就可以先攔截 XMLHttpRequest 對象的 new 操作,落實到代碼就是用 Proxy 的 construct 方法。在攔截操作裡,我們就做簡單的兩件事:

  • 實例化 built-in 對象 XMLHttpRequest
  • 用 Proxy 繼續攔截 built-in 對象 XMLHttpRequest 的實例
基於ES6的Proxy,100行代碼實現一個XMLHttpRequest的攔截


然後我們在上面的第二步接著深入,用 get 和 set 對實例進行攔截,下面我們重點看下這兩個方法裡做的事情。

  • get(target, p, receiver)
    • 普通屬性 進行 get 攔截操作,代碼如下、
基於ES6的Proxy,100行代碼實現一個XMLHttpRequest的攔截

上文有提到,有一部分屬性不是 writable,所以遇到這些屬性,我們在之後的 set 操作裡,會將其緩存進帶有前綴 _的同名屬性中,所以在 get 時,需要先判斷這些 _ 前綴的屬性是否存在進而進行讀取,而 writable 屬性則通過 getter 函數進行讀取。

    • 方法 進行攔截操作,代碼如下
基於ES6的Proxy,100行代碼實現一個XMLHttpRequest的攔截

攔截方法時,先判斷用戶是否提供了攔截函數,有的話執行並將結果記為 result,然後判斷 result 類型,如果是 true ,則終止方法。(這裡我加了一個功能,如果返回的是其他 truthy 值如 object 或者 function,可以將 result 當做新的參數傳入。)

  • set(target, p, value, receiver)
    • 事件屬性 進行攔截操作,代碼如下
基於ES6的Proxy,100行代碼實現一個XMLHttpRequest的攔截

很簡單,也是用戶是否提供了攔截函數,有的話先執行。

    • 普通屬性 進行 set 攔截操作,代碼如下
基於ES6的Proxy,100行代碼實現一個XMLHttpRequest的攔截

和上面類似的攔截操作,這裡需要注意一下 catch 裡的代碼,此處就是上文說的對不是 writable 的屬性進行的特殊操作。

最後,只需將上述代碼生成的 Proxy 對象實例 賦值給 全局的 buit-in 對象 XMLHttpRequest 就大功告成了。 至此,基本上就是所有的代碼了,在這裡總結一下:

ajax-proxy 使用 Proxy 先對 buit-in 對象 XMLHttpRequest 的 new 操作進行攔截,然後再創建一個 Proxy 實例,對 buit-in 對象 XMLHttpRequest 實例的 get 和 set進行攔截操作,最後將生成的 Proxy 對象實例 賦值給 全局的 buit-in 對象 XMLHttpRequest,Done!

結語

篇幅有限,有些細節沒有講清楚或者講的不對的地方請指出,更多的用法以及代碼

首先關注我,並且私信我回復“教程”即可獲取代碼!

對於本文你有其他的見解或想法歡迎評論區留言,謝謝!


分享到:


相關文章: