基於 ES6 的 Proxy ,100行代碼實現一個 XMLHttpRequest 的攔截核心 ajax-proxy

前言

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

關於 ES6 的 Proxy

這個東西其實也不新鮮了,不過因爲不支持 IE ,且 Safari 10 纔開始支持,用的時候一直當心翼翼的。一直在尋找一些最佳實踐,此次應該也算是一次練手。對它不太熟的同窗能夠看看 MDN上的ProxyECMAScript 6入門裏的Proxy 。這次實現,用到了它的getset以及 construct 方法。前端

關於 XMLHttpRequest

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

  • responseresponseTexttimeout這類的屬性,姑且稱之爲 普通屬性
  • 對應的,像 onreadystatechangeonprogressonload這類的屬性,則稱之爲 事件屬性
  • 固然,還有一些 opensendabort這類,稱之爲 方法
  • 這裏重點關注一個地方,有很大一部分屬性並非 writable 的,以下圖

8E62C51B-6350-4E14-B715-0E9ECF36CD3C.jpg

因此咱們在攔截這些屬性時,要作些特殊處理ios

原理解析

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

Ajax-hook 同樣,整體是採用代理模式,下面上個整體的原理圖:git

ajax-proxy.jpg

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

var xhr = new XMLHttpRequest()

將其實例化,那麼咱們就能夠先攔截 XMLHttpRequest 對象的 new 操做,落實到代碼就是用 Proxyconstruct 方法。在攔截操做裏,咱們就作簡單的兩件事:github

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

carbon.png

而後咱們在上面的第二步接着深刻,用 getset 對實例進行攔截,下面咱們重點看下這兩個方法裏作的事情。ajax

get(target, p, receiver)axios

  • 普通屬性進行get攔截操做,代碼以下carbon的副本.png 上文有提到,有一部分屬性不是 writable,因此遇到這些屬性,咱們在以後的 set 操做裏,會將其緩存進帶有前綴 _的同名屬性中,因此在 get 時,須要先判斷這些 _ 前綴的屬性是否存在進而進行讀取,而 writable 屬性則經過 getter 函數進行讀取。
  • 方法進行攔截操做,代碼以下carbon的副本2.png攔截方法時,先判斷用戶是否提供了攔截函數,有的話執行並將結果記爲 result,而後判斷 result 類型,若是是 true ,則終止方法。(這裏我加了一個功能,若是返回的是其餘 truthy 值如 object 或者 function,能夠將 result 當作新的參數傳入。)

set(target, p, value, receiver)瀏覽器

  • 事件屬性進行攔截操做,代碼以下carbon的副本3.png很簡單,也是用戶是否提供了攔截函數,有的話先執行。
  • 普通屬性進行 set 攔截操做,代碼以下carbon的副本4.png 和上面相似的攔截操做,這裏須要注意一下catch裏的代碼,此處就是上文說的對不是writable的屬性進行的特殊操做。

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

ajax-proxy 使用 Proxy 先對 buit-in 對象 XMLHttpRequestnew 操做進行攔截,而後再建立一個 Proxy 實例,對 buit-in 對象 XMLHttpRequest 實例的 getset進行攔截操做,最後將生成的 Proxy 對象實例 賦值給 全局的 buit-in 對象 XMLHttpRequest,Done!

最後

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

GitHub: github.com/LazyDuke/aj…

repo 裏還有對 XMLHttpRequestJQueryAjax 模塊以及 axios 進行攔截的測試用例,以爲有意思的點一下 Star 吧~

本文章容許免費轉載,但請註明原做者及原文連接。

相關文章
相關標籤/搜索