一比一還原axios源碼(零)—— 是結束亦是開始


  從vue2版本開始,vue-resource就不再被vue所維護和支持,官方也推薦使用axios,所以,從我使用axios至今,差不多有四五年了,這四五年的時間只能算是熟練應用,很多內部的實現和原理不清不楚,導致在開發的時候遇到問題,大多數情況都是憑借經驗來“猜測”出答案,這就導致內心深處十分的空虛和忐忑,就像是走路的時候腳步虛浮,跌跌撞撞,一點都不平穩。剛好最近的計划是看源碼,所以就從axios開始,詳細的去解讀整個axios的實現,希望這個系列既是筆記也是分享,讓大家知道原理,理解場景,懂得實現。ok,下面我們開始進入正文吧。

  axios本身的核心其實就是XMLHttpRequest,但是又不僅僅是XMLHttpRequest。簡單來說,一個庫或者一個框架,一個項目,它的核心內容都包含了兩部分:打包構建和核心源碼。那么本系列對打包構建部分一帶而過,只是提供了可以調試源碼的程度,並不會對打包構建說太多,一方面是自認為水平還達不到對構建也通透的程度,另外一方面,希望可以抽絲剝繭,去繁取簡,讓源碼不再是神秘的、不可觸及的,讓所有程度的前端同學,都可以從這系列中學到、用到、得到。

  哈哈,說多了,下面繼續說axios吧。剛才說到了一個項目包含兩部分:打包構建和核心源碼,那么在核心源碼里axios還可以繼續拆分出:js代碼、typescript聲明、單元測試、demo例子。我們這個系列,僅實現:輕量的打包、demo例子和js源碼三個部分。當然,或許后續有時間的話,還會把typescript和單元測試、打包構建也都聊一下,不過,那或許得等我學會的時候啦。哈哈。

  本系列會在每篇文章中,以axios的api入手,對比原生的XMLHttpRequest,會事先聊一下要實現的axiso API是如何使用的,然后根據該部分內容,逐步實現axios源碼。

  其次,我還會在gitHub上發布實現的源碼,每個章節都對應一個分支,可以具體看到代碼的逐步迭代過程。針對每一個章節,深入的學習,另外,雖然我實現的代碼盡可能的貼近axios的源碼,甚至有一些工具方法,都是完美復制的。但是,在大家看完本系列文章后,我還是建議大家去看看真正的源碼,自己fork一份,對比閱讀學習。

一、axios項目結構及生態簡介

1、axios打包

  我們先來看下axios完整的目錄結構,每一個文件的含義介紹在CATALOG.md中,大家可以去看下,在這里僅抽出一部分核心的內容說下。

  首先整個axios項目的打包構建使用了Grunt,通過Grunt配置一些流程操作,比如單元測試,打包等流程,Grunt算是整個項目構建的流程管控工具。其次,單元測試是用的mocha+karma的體系。然后打包,最終生成包結果是使用了webpack。其他的細節不多說,這不是本系列的重點。過~~

2、axios及其生態

  我們可以回顧整個axios的Tags,從最初的0.1.0版本到現在的0.25.0,整個項目的流程管理工具、單元測試工具等,都沒有變化,只是在逐漸迭代的過程中加入了typescript聲明,單元測試等。從功能上來說,最開始的axios其實是angular生態的一個模塊,只有簡單的請求方法,並沒有現在的cancelToken,interceptor等功能,隨着時代的變化,逐步分離出來成為獨立的ajax庫。更為詳細更新過程的大家可以去看axios的UPGRADE_GUIDE.md文檔。另外要說一下的是axios的生態,有很多可以配合axios使用的工具,詳情可以去axios的ECOSYSTEM.md文檔查看。

   其實上面說的都是屁話,毛用沒有~~~

二、ajax及其相關

  這小節我們來聊下客戶端與服務器通信的方式有哪些,着重介紹下ajax以及XMLHttpRequest,額外的,還會簡單介紹下WebSockets、EventSource、fetch等。ajax和XMLHttpRequest是重點,重點來了!!!

1、ajax和XMLHttpRequest

  眾所周知,ajax的全稱是Asynchronous JavaScript and XML,即異步的Javascript和XML,通過使用ajax可以使用js來發送請求,服務器返回的數據再通過前端js代碼,來渲染到頁面上。ajax本身並不是一項新技術,而是一些技術的集合。那么,在開始了解ajax之前,假如沒有ajax,客戶端如何與服務器交互呢?

  首先,可以通過iframe,其次還有表單提交,超鏈接等方式。或者,比較傳統的可以通過jsp等后端語言技術來實現。但是,客戶端與服務器通信的目的我們實現了,但是有一個核心的問題仍舊無法解決,也就是異步。每一次的表單提交,超鏈接等,都要刷新整個頁面,導致我們的交互體驗並不是十分友好。所以,ajax的出現,解決了部分數據刷新的問題,使得數據的獲取和局部渲染變得更為便捷。

  上面說道,ajax並不是一個新的技術,而是幾種技術的組合,那么其中最為核心的就是XMLHttpRequest。具體的XMLHttpRequest文檔可以參考MDN。這里不再多說。

  以下是一個最簡單的XMLHttpRequest請求例子,我們通過這個簡單的例子,來看看XMLHttpRequest的一些相關api,這是我們后續實現axios的基礎,首先,我們在本地創建一個html文件,代碼如下:

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body></body>
  <script>
    const data = { a: 1, b: 2 };
    var request = new XMLHttpRequest();
    request.open("POST", "http://httpbin.org/post?a=1&b=2");
    request.send(data);
  </script>
</html>

   我想來想去,核心的代碼就這些,很簡單,其實就三行代碼,但是我們卻可以來分析下這三行代碼。首先我們創建一個XMLHttpRequest對象,然后通過這個對象實例,調用open方法,然后再調用send方法。那么第一個問題就是,如何拼接url的get請求的query參數?我們知道axios是傳入的params對象,所以這就是我要實現的源碼之一,再然后,data是個對象,但是body的請求體接收的是一個json字符串,所以我們也要轉換。到了這里我們簡單的了解了XMLHttpRequest的核心基礎API。那么下面我們結合rollup打包工具,來生成一個我們寫好的ajax請求的例子。

  要注意,這個例子只是一個簡單的XMLHttpRequest對象的應用,和axios無關又有關。rollup打包的代碼就十來行,大家可以在c0分支中的rollup.config.js中查看,有興趣的可以關注下,沒興趣的可以不用關注,直接把項目npm run build就可以了。

  然后我們在dist的目錄下,創建個index.html,內容如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./axios.umd.js"></script>
    <script>
      zakingAxios();
    </script>
  </body>
</html>

  其實就是引入我們打包后的文件,然后調用。然后打開index.html文件,就可以看到打印出來的axios字符串了。哦對了,lib下的axios文件中的代碼是這樣的:

function axios() {
  console.log("axios");
}
export default axios;

  OK。那么下面我們就來寫一個例子。哦對,我們請求的接口來自於這個地址:https://httpbin.org/。我們先來看下代碼:

function axios() {
  const xhr = new XMLHttpRequest();
  xhr.open("GET", "https://httpbin.org/get");
  xhr.send();
}
export default axios;

  然后npm run build重新打包下就可以在控制台看到get請求了。但是這只是最簡單的get請求,那我們來增加一點需求。我希望可以給get請求傳參數,怎么辦?

  xhr.open("GET", "https://httpbin.org/get?a=1&b=1&c=1");

  那,我用get請求是否可以傳遞數組和對象呢?ok,這是我們這篇文章留下的第一個問題。跳過,我們繼續來增加需求,現在get請求傳參數可以了,我想用post請求並且傳遞個對象,咋整?這是我們在開發中最常見的場景了。我們把代碼改一下:

function axios() {
  const xhr = new XMLHttpRequest();
  xhr.open("POST", "https://httpbin.org/post");
  xhr.send({ a: 1, b: 2 });
}
export default axios;

   跑起來后我們發現一個問題。誒?怎么會這樣?

 

  XMLHttpRequest是不接受對象形式的body的,那么我們把它轉換成JSON字符串呢?

 xhr.send(JSON.stringify({ a: 1, b: 2 }));

  我們發現可以了~~那么接下來,我希望可以收到響應的body咋辦呢?

function axios() {
  const xhr = new XMLHttpRequest();
  xhr.open("POST", "https://httpbin.org/post");
  xhr.send(JSON.stringify({ a: 1, b: 2 }));
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
      console.log(xhr.response);
      console.log(xhr.responseText);
    }
  };
}
export default axios;

  我們可以通過判斷XMLHttpRequest實例對象上的readyState和status來判斷請求是否結束,然后獲取xhr上的response或者responseText。那么,第二個問題,response和responseText有啥區別?問題的答案可以在這里尋找:https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest關於readyState和status也都可以在這個鏈接找到,或者說,關於XMLHttpRequest相關的方法和屬性都可以在這里查詢。

  OK,我們完整的發起了一個POST請求,例子就到此為止,深入的內容我們會在后面的章節實現axios的時候再詳細介紹。點到為止。 

2、EventSource

  EventSource可以讓服務器主動發送數據到我們的代碼中, 當不需要以消息形式將數據從客戶端發送到服務器時,這使它們成為絕佳的選擇。例如,對於處理社交媒體狀態更新,新聞提要或將數據傳遞到客戶端存儲機制(如IndexedDB或Web存儲)之類的,EventSource無疑是一個有效方案(這段話是抄的)。具體內容可查看MDN

3、WebSockets

  這個東西相信大家也有一定的了解,它可以在用戶的瀏覽器和服務器之間打開交互式通信會話。使用此API,您可以向服務器發送消息並接收事件驅動的響應,而無需通過輪詢服務器的方式以獲得響應,可參考MDN

4、ActiveXObject

  這個東西有點陌生,而且有點復雜, 它可以操作文件、文件夾,獲取相關信息,發起http請求等,它是一個復雜的功能龐大的對象或者說接口,http請求功能只不過是它的一小部分,發起請求可以通過如下的形式,在之前IE兼容的時候,如果沒有XMLHttpRequest,也會使用到ActiveXObject:

new window.ActiveXObject("Mscrosoft.XMLHttp")

  這個也簡單提一下,過~~,有興趣自行百度!

4、Fetch

  這個東西想必大家都比較熟悉,或多或少聽說過,算是XMLHttpRequest的升級版,也是用來在瀏覽器中發起http請求。fetch是用了promise,簡潔了用法。並且采用模塊化設計,api分散在多個對象上,如果要展開的話內容很多,所以大家可以去本章的參考資料中查看,阮一峰大神寫的很好了,這里也不多說。鏈接貼在了最后。

 

三、目錄

  1. 一比一還原axios源碼(一)—— 發起第一個請求 
  2. 一比一還原axios源碼(二)—— 請求響應處理
  3. 一比一還原axios源碼(三)—— 錯誤處理
  4. 一比一還原axios源碼(四)—— Axios類
  5. 一比一還原axios源碼(五)—— 攔截器
  6. 一比一還原axios源碼(六)—— 配置化
  7. 一比一還原axios源碼(七)—— 取消功能
  8. 一比一還原axios源碼(八)—— 其他功能

四、階段閑聊

  終於,寫完了整個axios的實現,寫完了之后發現其實axios的實現源碼並不是十分復雜,但是需要一定的了解和熟悉,需要一定的閱讀理解能力。我就不多說了,最后說說這一系列的文章吧。首先,整個zaking-axios的example部分的代碼,來源於慕課網《下一代前端開發語言 TypeScript從零重構axios》這個視頻課,ustbhuangyi老師講的真的很好。我在學習的過程中就想自己實現一遍,但是課程中有些代碼是經過老師優化的,並且加上了ts、jest等。一方面由於我的水平有限,另外一方面我又希望可以讓初學者更容易學習,所以,整個zaking-axios並沒有加那么多,你只要有基本的js基礎,就能看懂我寫的zaking-axios。當然,或許后面時間充足,我也會加上ts和jest,也會盡我所能的去優化其中的代碼。

  最后,希望大家在閱讀的過程中有什么疑問或者發現了什么問題,都可以及時交流,我也會盡快回復。

  最后的最后,感謝!

參考資料,附:

  1. https://www.ruanyifeng.com/blog/2020/12/fetch-tutorial.html
  2. https://www.zhihu.com/question/27771468
  3. https://xhr.spec.whatwg.org/#the-status-attribute
  4. https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
  5. 完整代碼:https://github.com/zakingwong/zaking-axios


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM