axios实现原理
axios的封装可以去看上一篇文章
XMLHttpRequest
open()方法
open()
方法接收三个参数:
- 第一个参数
method
:要发送的请求的类型。比如GET
、POST
、PUT
、DELETE
等。 - 第二个参数
url
:请求的URL
。 - 第三个参数
async
:是否异步发送请求的布尔值。true
为异步发送请求。
const xhr = new XMLHttpRequest()
xhr.open('get', '/userInfo', true)
send()方法
send()
方法接收一个参数:
第一个参数data
:作为请求主体发送的数据。如果不需要通过请求主体发送数据,则必须传入null
。
setRequestHeader()方法
setRequestHeader()
方法可以设置自定义的请求头部信息。
const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xhr.setRequestHeader('MyHeader', 'MyValue')
xmlhttp.send()
readyState属性和onreadystatechange事件
0(UNSENT
)未初始化。尚未调用open()
方法。
1(OPENED
)启动。已经调用open()
方法,但没有调用send()
方法。
2(SEND
)发送。已经调用send()
方法,但尚未接收到响应。
3(LOADING
)接收。已经接收到部分响应数据。
4(DONE
)完成。已经接收到全部响应数据。
只要readyState
属性的值发生变化,都会触发一次onreadystatechange
事件。readyState
值为4
的阶段做处理,这时所有数据都已经就绪。
axios的使用方法:
import axios from 'axios';
axios(config) // 直接传入配置
axios(url[, config]) // 传入url和配置
axios[method](url[, option]) // 直接调用请求方式方法,传入url和配置
axios[method](url[, data[, option]]) // 直接调用请求方式方法,传入data、url和配置
axios.request(option) // 调用 request 方法
const axiosInstance = axios.create(config)
// axiosInstance 也具有以上 axios 的能力
axios.all([axiosInstance1, axiosInstance2]).then(axios.spread(response1, response2))
// 调用 all 和传入 spread 回调
核心基于XMLHttpRequest readyState 0 1 2 3 4
state为4 statuas为2xx 304则走onload事件
实现一个简易版axios
构建一个Axios
构造函数,核心代码为request
class Axios {
constructor() {
}
request(config) {
return new Promise(resolve => {
const {url = '', method = 'get', data = {}} = config;
// 发送ajax请求
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onload = function() {
console.log(xhr.responseText)
resolve(xhr.responseText);
}
xhr.send(data);
})
}
}
导出axios
实例
// 最终导出axios的方法,即实例的request方法
function CreateAxiosFn() {
let axios = new Axios();
let req = axios.request.bind(axios);
return req;
}
// 得到最后的全局变量axios
let axios = CreateAxiosFn();//函数返回axios实例上的request方法
上述就已经能够实现axios({ })
这种方式的请求
下面是来实现下axios.method()
这种形式的请求
// 定义get,post...方法,挂在到Axios原型上
const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];
methodsArr.forEach(met => {
Axios.prototype[met] = function() {
console.log('执行'+met+'方法');
// 处理单个方法
if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config])
return this.request({
method: met,
url: arguments[0],
...arguments[1] || {}
})
} else { // 3个参数(url[,data[,config]])
return this.request({
method: met,
url: arguments[0],
data: arguments[1] || {},
...arguments[2] || {}
})
}
}
})
将Axios.prototype
上的方法搬运到request
上
首先实现个工具类,实现将b
方法混入到a
,并且修改this
指向
const utils = {
extend(a,b, context) {
for(let key in b) {
if (b.hasOwnProperty(key)) {
if (typeof b[key] === 'function') {
a[key] = b[key].bind(context);
} else {
a[key] = b[key]
}
}
}
}
}
修改导出的方法
function CreateAxiosFn() {
let axios = new Axios();
let req = axios.request.bind(axios);
// 增加代码
utils.extend(req, Axios.prototype, axios)//将axios原型上的get、post等请求方法绑定到req方法上,其this指向axios实例
return req;返回的是new出来的axios上的方法
}
构建拦截器的构造函数
class InterceptorsManage {
constructor() {
this.handlers = [];
}
use(fullfield, rejected) {
this.handlers.push({
fullfield,
rejected
})
}
}
实现axios.interceptors.response.use
和axios.interceptors.request.use
class Axios {
constructor() {
// 新增代码
this.interceptors = {
request: new InterceptorsManage,
response: new InterceptorsManage
}
}
request(config) {
...
}
}
执行语句axios.interceptors.response.use
和axios.interceptors.request.use
的时候,实现获取axios
实例上的interceptors
对象,然后再获取response
或request
拦截器,再执行对应的拦截器的use
方法
把Axios
上的方法和属性搬到request
过去
function CreateAxiosFn() {
let axios = new Axios();
let req = axios.request.bind(axios);
// 混入方法, 处理axios的request方法,使之拥有get,post...方法
utils.extend(req, Axios.prototype, axios)
// 新增代码
utils.extend(req, axios)
return req;
}
现在request
也有了interceptors
对象,在发送请求的时候,会先获取request
拦截器的handlers
的方法来执行
首先将执行ajax
的请求封装成一个方法
request(config) {
this.sendAjax(config)
}
sendAjax(config){
return new Promise(resolve => {
const {url = '', method = 'get', data = {}} = config;
// 发送ajax请求
console.log(config);
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onload = function() {
console.log(xhr.responseText)
resolve(xhr.responseText);
};
xhr.send(data);
})
}
获得handlers
中的回调
request(config) {
// 拦截器和请求组装队列
let chain = [this.sendAjax.bind(this), undefined] // 成对出现的,失败回调暂时不处理
// 请求拦截
this.interceptors.request.handlers.forEach(interceptor => {
chain.unshift(interceptor.fullfield, interceptor.rejected)
})
// 响应拦截
this.interceptors.response.handlers.forEach(interceptor => {
chain.push(interceptor.fullfield, interceptor.rejected)
})
// 执行队列,每次执行一对,并给promise赋最新的值
let promise = Promise.resolve(config);
while(chain.length > 0) {
promise = promise.then(chain.shift(), chain.shift())
}
return promise;
}
chains
大概是['fulfilled1','reject1','fulfilled2','reject2','this.sendAjax','undefined','fulfilled2','reject2','fulfilled1','reject1']
这种形式
这样就能够成功实现一个简易版axios
参考:https://vue3js.cn/interview/vue/axiosCode.html#%E4%BA%8C%E3%80%81%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%AE%80%E6%98%93%E7%89%88axios