從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分散在多個物件上,如果要展開的話內容很多,所以大家可以去本章的參考資料中檢視,阮一峰大神寫的很好了,這裡也不多說。連結貼在了最後。
三、目錄
參考資料,附: