ajax、fetch、axios
ajax
ajax可以在不更新全局的情況下更新局部頁(yè)面。通過在與服務(wù)器進(jìn)行數(shù)據(jù)交換,可以使網(wǎng)頁(yè)實(shí)現(xiàn)異步更新。
ajax的原理就是通過xhr對(duì)象來向服務(wù)器發(fā)起異步請(qǐng)求,從服務(wù)器獲得數(shù)據(jù),然后用js來操作dom更新頁(yè)面。領(lǐng)導(dǎo)想找小李匯報(bào)一下工作,就委托秘書去叫小李,自己就接著做其他事情,直到秘書告訴他小李已經(jīng)到了,最后小李跟領(lǐng)導(dǎo)匯報(bào)工作。ajax請(qǐng)求數(shù)據(jù)流程與“領(lǐng)導(dǎo)想找小李匯報(bào)一下工作”類似,上述秘書就相當(dāng)于xmlhttprequest對(duì)象,領(lǐng)導(dǎo)相當(dāng)于瀏覽器,響應(yīng)數(shù)據(jù)相當(dāng)于小李。瀏覽器可以發(fā)送http請(qǐng)求后,接著做其他事情,等收到xhr返回來的數(shù)據(jù)再進(jìn)行操作。
創(chuàng)建ajax
// 1. 創(chuàng)建 xmlhttprequest 實(shí)例 let xhr = xmlhttprequest() // 2. 打開和服務(wù)器的連接 xhr.open('get', 'url') // 3.發(fā)送 xhr.send() // 4. 接收變化。 xhr.onreadystatechange = () => { if(xhr.readystate == 4 && xhr.status == 200){ // readystate: ajax 狀態(tài),status:http 請(qǐng)求狀態(tài) console.log(xhr.responsetext); //響應(yīng)主體 } }
- 創(chuàng)建ajax實(shí)例:let xhr = new xmlhttprequest()
- 打開請(qǐng)求,配置請(qǐng)求前的配置項(xiàng):xhr.open([http method], [url], [async], [username], [userpass])
- 通過xmlhttprequest.open()方法與服務(wù)器建立連接
- 發(fā)送請(qǐng)求:xmlhttprequest.send() 方法中如果 ajax 請(qǐng)求是異步的則這個(gè)方法發(fā)送請(qǐng)求后就會(huì)返回,如果ajax請(qǐng)求是同步的,那么請(qǐng)求必須知道響應(yīng)后才會(huì)返回。
- 通過xmlhttprequest對(duì)象的onreadystatechange事件監(jiān)聽服務(wù)器端的通信狀態(tài)
- 接收數(shù)據(jù)并進(jìn)行處理
- 將處理后的結(jié)果更新到頁(yè)面上
ajax的缺點(diǎn):
- 本是針對(duì)mvc架構(gòu),不符合前端mvvm的浪潮
- 基于原生的xhr開發(fā)
- 配置和調(diào)用方式混亂
axios原理
axios是使用promise封裝的ajax,它內(nèi)部有兩個(gè)攔截器,分別是request攔截器和response攔截器。
- 請(qǐng)求攔截器的作用是在請(qǐng)求發(fā)送之前進(jìn)行一些操作,例如在每個(gè)請(qǐng)求體上加入token
- 響應(yīng)攔截器的作用是接收到響應(yīng)后做的一些操作,例如登錄失效后需要重新登錄跳轉(zhuǎn)到登錄頁(yè)
axios的特點(diǎn)
- 由瀏覽器端發(fā)起請(qǐng)求,在瀏覽器中創(chuàng)建xhr
- 支持promise api
- 監(jiān)聽請(qǐng)求和返回
- 更好的格式化,自動(dòng)將數(shù)據(jù)轉(zhuǎn)換為json數(shù)據(jù)
- 安全性更高,可抵御csrf攻擊
axios常用的方法
axios常用的方法有get
、post
、put
、patch
、delete
等。其中get
和post
返回的都是promise
對(duì)象,可以使用promise
方法
axios.get(url[, config])
:get請(qǐng)求用于列表和信息查詢
axios.get('apiurl', { param: { id: 1 } // param 中的的鍵值對(duì)最終會(huì) ? 的形式,拼接到請(qǐng)求的鏈接上,發(fā)送到服務(wù)器。 }).then(res => { console.log(res); }) .catch( error => { console.log(error) }
axios.delete(url[, config])
:刪除
axios.delete('apiurl', { params: { id: 1 }, timeout: 1000 })
axios.post(url[, data[, config]])
:post請(qǐng)求用于信息的添加
axios.post('apiurl',{ user: '小新', age: 18 }).then( res => { console.log(res); }) .catch( error => { console.log(error) }
axios.put(url[, data[, config]])
:更新操作
axios.put('apiurl', { name: '小新', })
axios.patch(url[, data[, config]])
:更新操作
axios.patch('apiurl', { id: 13, },{ timeout: 1000, })
put和patch的區(qū)別
patch
方法用來更新局部資源,假設(shè)我們有一個(gè)userinfo,里面有userid,username,usergender等10個(gè)字段??赡愕木庉嫻δ芤?yàn)樾枨?,在某個(gè)特別的頁(yè)面里只能修改username,這個(gè)時(shí)候就可以使用patch
。
put
也適用于更新數(shù)據(jù),但必須提供完整的資源對(duì)象。
axios相關(guān)配置
- url:用于請(qǐng)求服務(wù)器的url
- method:請(qǐng)求方法,默認(rèn)為get
- baseurl:會(huì)自動(dòng)加到url前面
- proxy:用于配置代理
- transformrequest:允許在服務(wù)器發(fā)送請(qǐng)求之前修改請(qǐng)求數(shù)據(jù)
axios攔截器執(zhí)行順序問題
- 請(qǐng)求攔截:axios的請(qǐng)求攔截器會(huì)先執(zhí)行最后指定的回調(diào)函數(shù),再依次向前執(zhí)行
- 響應(yīng)攔截:axios的響應(yīng)攔截器會(huì)先執(zhí)行最先執(zhí)行的回調(diào)函數(shù),再依次向前執(zhí)行
例如:
axios.interceptors.request.use(config => { console.log(`請(qǐng)求攔截1`); return config; }); axios.interceptors.request.use(config => { // 在發(fā)送請(qǐng)求之前做些什么 console.log(`請(qǐng)求攔截2`); return config; }); // 添加響應(yīng)攔截器 axios.interceptors.response.use(response => { // 對(duì)響應(yīng)數(shù)據(jù)做點(diǎn)什么 console.log(`成功的響應(yīng)攔截1`); return response.data; }); // 添加響應(yīng)攔截器 axios.interceptors.response.use(response => { // 對(duì)響應(yīng)數(shù)據(jù)做點(diǎn)什么 console.log(`成功的響應(yīng)攔截2`); return response; }); // 發(fā)送請(qǐng)求 axios.get('/posts') .then(response => { console.log('成功了'); })
執(zhí)行結(jié)果為
console.log("請(qǐng)求攔截2");
console.log("請(qǐng)求攔截1");
console.log("成功的響應(yīng)攔截1");
console.log("成功的響應(yīng)攔截2");
console.log("成功了");
為什么axios中需要攔截器
在spa應(yīng)用中,通常會(huì)使用token進(jìn)行用戶身份認(rèn)證,這就要求每次請(qǐng)求必須攜帶用戶的身份信息,針對(duì)這個(gè)需求,為了避免在每個(gè)請(qǐng)求中單獨(dú)處理,我們可以通過封裝統(tǒng)一的request函數(shù)來為每隔請(qǐng)求統(tǒng)一添加token信息。
但如果想為某些請(qǐng)求添加緩存時(shí)間或者控制某些請(qǐng)求的調(diào)用頻率的話,我們就需要不斷地修改request函數(shù)來擴(kuò)展對(duì)應(yīng)的功能。此時(shí),如果在考慮對(duì)響應(yīng)進(jìn)行統(tǒng)一處理,我們的request函數(shù)將變得越來越龐大,也越來越難維護(hù)。所以axios為我們提供了攔截器。
為什么請(qǐng)求攔截2會(huì)在請(qǐng)求攔截1之前執(zhí)行呢?
在axios
源碼中將發(fā)送請(qǐng)求分為了請(qǐng)求攔截器、發(fā)送請(qǐng)求、響應(yīng)攔截器、相應(yīng)回調(diào),通過promise的鏈?zhǔn)秸{(diào)用將這些部分結(jié)合起來了,這樣就得到了發(fā)送請(qǐng)求拿到數(shù)據(jù)的全部過程。
下面分析源碼:
- 代碼開始構(gòu)建了一個(gè)config配置對(duì)象,用于第一次執(zhí)行promise返回一個(gè)成功的promise
- 最核心的數(shù)組chain,這個(gè)數(shù)組中保存了請(qǐng)求攔截器、響應(yīng)攔截器和發(fā)送請(qǐng)求函數(shù)。該數(shù)組中間放的是發(fā)送請(qǐng)求的函數(shù),左邊放的是請(qǐng)求攔截器,右邊放的是響應(yīng)攔截器。在第一步中返回的promise對(duì)象,將遍歷chain數(shù)組逐一執(zhí)行里面的函數(shù),并返回新的promise對(duì)象
- 往數(shù)組中添加請(qǐng)求攔截函數(shù),依照axios請(qǐng)求的執(zhí)行順序,請(qǐng)求攔截器應(yīng)該在發(fā)送請(qǐng)求之前執(zhí)行,故應(yīng)該添加在發(fā)送請(qǐng)求函數(shù)的前面,使用unshift方法
- 往數(shù)組中添加響應(yīng)攔截器函數(shù),依照axios請(qǐng)求的執(zhí)行順序,響應(yīng)攔截器應(yīng)該在發(fā)送請(qǐng)求之后執(zhí)行,故應(yīng)該添加在發(fā)送請(qǐng)求函數(shù)的后面,所以使用的是數(shù)組的push方法
- promise遍歷執(zhí)行,每次從chain中取出兩個(gè) 函數(shù)執(zhí)行(一個(gè)成功回調(diào),一個(gè)失敗回調(diào))
- 最后返回一個(gè)promise對(duì)象,用于執(zhí)行響應(yīng)數(shù)據(jù)的回調(diào)
fetchfetch
是http請(qǐng)求數(shù)據(jù)的方式,它使用promise,但不使用回調(diào)函數(shù)。fetch
采用模塊化設(shè)計(jì),通過數(shù)據(jù)流處理數(shù)據(jù),對(duì)于請(qǐng)求大文件或網(wǎng)速慢的情況相當(dāng)有用。默認(rèn)情況下fetch不會(huì)接收或發(fā)送cookies。
優(yōu)點(diǎn):
- 采用模塊化思想,將輸入、輸出、狀態(tài)跟蹤分離
- 基于promise,返回一個(gè)promise對(duì)象
缺點(diǎn):
- 過于底層,有很多狀態(tài)碼沒有進(jìn)行封裝
- 無法阻斷請(qǐng)求
- 兼容性差無法檢測(cè)請(qǐng)求進(jìn)度
fetch、ajax與axios的區(qū)別
- 傳統(tǒng)的ajax利用的是
hmlhttprequest這個(gè)對(duì)象
,和后端進(jìn)行交互。 - 而
jqury ajax
是對(duì)原生xhr
的封裝,多請(qǐng)求間有嵌套的話就會(huì)出現(xiàn)回調(diào)地獄的問題。 axios
使用promise
封裝xhr
,解決了回調(diào)地獄的問題。而fetch
沒有使用xhr
,使用的是promise
fetch和ajax比有什么優(yōu)點(diǎn)
fetch
使用的是promise
,方便使用異步,沒有回調(diào)地獄的問題。
總結(jié)
ajax
是一種web數(shù)據(jù)交互的方式,它可以使頁(yè)面在不重新加載的情況下請(qǐng)求數(shù)據(jù)并進(jìn)行局部更新,它內(nèi)部使用了xhr
來進(jìn)行異步請(qǐng)求。ajax
在使用xhr
發(fā)起異步請(qǐng)求時(shí)得到的是xml
格式的數(shù)據(jù),如果想要json格式,需要進(jìn)行額外的轉(zhuǎn)換;ajax
本身針對(duì)的是mvc框架
,不符合現(xiàn)在的mvvm架構(gòu)
;ajax
有回調(diào)地獄問題;ajax
的配置復(fù)雜
而fetch
是xhr的代替品,它基于promise
實(shí)現(xiàn)的,并且不使用回調(diào)函數(shù),它采用模塊化結(jié)構(gòu)設(shè)計(jì),并使用數(shù)據(jù)流進(jìn)行傳輸,對(duì)于大文件和網(wǎng)速慢的情況非常友好。但是fetch
不會(huì)對(duì)請(qǐng)求和響應(yīng)進(jìn)行監(jiān)聽;不能阻斷請(qǐng)求;過于底層,對(duì)一些狀態(tài)碼沒有封裝;兼容性差。
axios
是基于promise
對(duì)xhr
進(jìn)行封裝,它內(nèi)部封裝了兩個(gè)攔截器,分別是請(qǐng)求攔截器和響應(yīng)攔截器。請(qǐng)求攔截器用于在請(qǐng)求發(fā)出之前進(jìn)行一些操作,比如:設(shè)置請(qǐng)求體,攜帶cookie、token等;響應(yīng)攔截器用于在得到響應(yīng)后進(jìn)行一些操作,比如:登錄失效后跳轉(zhuǎn)到登錄頁(yè)面重新登錄。axios
有g(shù)et、post、put、patch、delete等方法。axios可以對(duì)請(qǐng)求和響應(yīng)進(jìn)行監(jiān)聽;返回promise
對(duì)象,可以使用promise
的api;返回json
格式的數(shù)據(jù);由瀏覽器發(fā)起請(qǐng)求;安全性更高,可以抵御csrf攻擊。
axios源碼分析
axios的執(zhí)行流程
- 使用axios.create創(chuàng)建單獨(dú)的實(shí)例,或直接使用axios實(shí)例
- 對(duì)于axios調(diào)用進(jìn)入到request()中進(jìn)行處理
- 執(zhí)行請(qǐng)求攔截器
- 請(qǐng)求數(shù)據(jù)轉(zhuǎn)換器,將傳入的數(shù)據(jù)進(jìn)行處理,比如
json.stringify(data)
- 執(zhí)行適配器,判斷是瀏覽器端還是node端,以執(zhí)行不同的方法
- 響應(yīng)數(shù)據(jù)轉(zhuǎn)換器,對(duì)服務(wù)器端的數(shù)據(jù)進(jìn)行處理,比如
json.parse(data)
- 執(zhí)行響應(yīng)攔截器,對(duì)服務(wù)器端數(shù)據(jù)進(jìn)行處理,比如token失效跳轉(zhuǎn)到登錄頁(yè)
- 返回?cái)?shù)據(jù)
入口文件(lib/axios.js)
導(dǎo)出的axios就是 實(shí)例化后的對(duì)象,還在其上掛載create
方法,以供創(chuàng)建獨(dú)立的實(shí)例,實(shí)現(xiàn)實(shí)例之間互不影響。
// 創(chuàng)建實(shí)例過程的方法 function createinstance(defaultconfig) { return instance; } // 實(shí)例化 var axios = createinstance(defaults); // 創(chuàng)建獨(dú)立的實(shí)例,隔離作用域 axios.create = function create(instanceconfig) { return createinstance(mergeconfig(axios.defaults, instanceconfig)); }; // 導(dǎo)出實(shí)例 module.exports = axios;
createinstance()
function createinstance(defaultconfig) { // 實(shí)例化,創(chuàng)建一個(gè)上下文 var context = new axios(defaultconfig); // 平時(shí)調(diào)用的 get/post 等等請(qǐng)求,底層都是調(diào)用 request 方法 // 將 request 方法的 this 指向 context(上下文),形成新的實(shí)例 var instance = bind(axios.prototype.request, context); // axios.prototype 上的方法 (get/post...)掛載到新的實(shí)例 instance 上, // 并且將原型方法中 this 指向 context utils.extend(instance, axios.prototype, context); // axios 屬性值掛載到新的實(shí)例 instance 上 // 開發(fā)中才能使用 axios.default/interceptors utils.extend(instance, context); return instance; }
createinstance
執(zhí)行流程:
- 通過構(gòu)造函數(shù)
axios
創(chuàng)建實(shí)例context
,作為下面request
方法的上下文(this指向) - 將
axios.prototype.request
方法作為實(shí)例使用,并把this
指向context
,形成新的實(shí)例instance
- 將構(gòu)造函數(shù)
axios.prototype
上的方法掛載到新的實(shí)例instance
上,然后將原型各個(gè)方法中的this
指向context
,這樣才能使用get、post
等方法 - 將
axios
的屬性掛載到instance
上
可以看到axios不是簡(jiǎn)單的創(chuàng)建實(shí)例context,而是在context上進(jìn)行this綁定形成新的實(shí)例,然后將axios屬性和請(qǐng)求方法掛載到新的實(shí)例上
攔截器(lib/core/interceptormanager.js)
攔截器涉及一個(gè)屬性和三個(gè)方法:
- handler:存放use注冊(cè)的回調(diào)函數(shù)
- use:注冊(cè)成功和失敗的回調(diào)函數(shù)
- eject:刪除注冊(cè)過的函數(shù)
- foreach:遍歷回調(diào)函數(shù)
function interceptormanager() { // 存放 use 注冊(cè)的回調(diào)函數(shù) this.handlers = []; } interceptormanager.prototype.use = function use(fulfilled, rejected, options) { // 注冊(cè)成功和失敗的回調(diào)函數(shù) this.handlers.push({ fulfilled: fulfilled, rejected: rejected, ... }); return this.handlers.length - 1; }; interceptormanager.prototype.eject = function eject(id) { // 刪除注冊(cè)過的函數(shù) if (this.handlers[id]) { this.handlers[id] = null; } }; interceptormanager.prototype.foreach = function foreach(fn) { // 遍歷回調(diào)函數(shù),一般內(nèi)部使用多 utils.foreach(this.handlers, function foreachhandler(h) { if (h !== null) { fn(h); } }); };
dispatchrequest(lib/core/dispatchrequest.js)
dispatchrequest主要做了以下操作:
- transformrequest: 對(duì) config 中的 data 進(jìn)行加工,比如對(duì) post 請(qǐng)求的 data 進(jìn)行字符串化(json.stringify(data))
- adapter:適配器,包含瀏覽器端 xhr 和 node 端的 http
- transformresponse: 對(duì)服務(wù)端響應(yīng)的數(shù)據(jù)進(jìn)行加工,比如 json.parse(data)
取消請(qǐng)求(lib/cancel/canceltoken.js)
var canceltoken = axios.canceltoken; var source = canceltoken.source(); axios.get('/user/12345', { canceltoken: source.token }).catch(function(thrown) { if (axios.iscancel(thrown)) { console.log('request canceled', thrown.message); } else { // 處理錯(cuò)誤 } }); // 取消請(qǐng)求(message 參數(shù)是可選的) source.cancel('operation canceled by the user.');
- canceltoken 掛載 source 方法用于創(chuàng)建自身實(shí)例,并且返回 {token, cancel}
- token 是構(gòu)造函數(shù) canceltoken 的實(shí)例,cancel 方法接收構(gòu)造函數(shù) canceltoken 內(nèi)部的一個(gè) cancel 函數(shù),用于取消請(qǐng)求
- 創(chuàng)建實(shí)例中,有一步是創(chuàng)建處于 pengding 狀態(tài)的 promise,并掛在實(shí)例方法上,外部通過參數(shù) canceltoken 將實(shí)例傳遞進(jìn) axios 內(nèi)部,內(nèi)部調(diào)用 canceltoken.promise.then 等待狀態(tài)改變
- 當(dāng)外部調(diào)用方法 cancel 取消請(qǐng)求,pendding 狀態(tài)就變?yōu)?resolve,即取消請(qǐng)求并且拋出 reject(message)
總結(jié)
- 為了支持 axios() 簡(jiǎn)潔寫法,內(nèi)部使用 request 函數(shù)作為新實(shí)例
- 使用 promsie 鏈?zhǔn)秸{(diào)用的巧妙方法,解決順序調(diào)用問題
- 數(shù)據(jù)轉(zhuǎn)換器方法使用數(shù)組存放,支持?jǐn)?shù)據(jù)的多次傳輸與加工
- 適配器通過兼容瀏覽器端和 node 端,對(duì)外提供統(tǒng)一 api
- 取消請(qǐng)求這塊,通過外部保留 pendding 狀態(tài),控制 promise 的執(zhí)行時(shí)機(jī)
到此這篇關(guān)于一文掌握ajax、fetch和axios的區(qū)別對(duì)比的文章就介紹到這了,更多相關(guān)ajax、fetch和axios的比較內(nèi)容請(qǐng)搜索碩編程以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持碩編程!