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

前言

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

關於 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 的,如下圖

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

原理解析

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

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

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

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

實例化 built-in 對象 XMLHttpRequest用 Proxy 繼續攔截 built-in 對象 XMLHttpRequest 的實例


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

get(target, p, receiver)普通屬性 進行 get 攔截操作,代碼如下、

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

方法 進行攔截操作,代碼如下

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

set(target, p, value, receiver)事件屬性 進行攔截操作,代碼如下

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

普通屬性 進行 set 攔截操作,代碼如下

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

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

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

結語

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

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

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