自从Axios
成功打入Vue
全家桶之后,便开始火的一塌糊涂!截止到目前,其在github
上的star
即将突破80k
!可以说Axios
是当下前端界最流行的ajax
请求库,可(jue
)能(dui
)没有之一!
即然Axios
人气如此之高,那么阅读并研究它的源码也是非常有必要的,因为这样不仅可以让自己少走很多弯路,还会对作者多年的编程思想以及经验进行猎取,从中抽象出一些架构及模式性的高级内容,最终提高自己的实现能力和技巧,让自己变得更加强大!
(啰嗦句:阅读源码的换确确可以提升自身的编码水平,但需要你拥有一定相关经验基础以及相对领域的认知,否则看源码绝对是在浪费时间!为什么?因为你看不懂!)
本系列文章将会对Axios
源码思想进行精简提炼,从而将大家所关心的一些内部原理问题进行解答。
一、封装一个简易版本的xhr + promise
的异步ajax
请求库
由于axios
是一个基于xhr + promise
的异步ajax
请求库,所以咱们可以先单纯的封装一个:
function axios(config){
// 将请求方式全部转为大写
const method = (config.method || "get").toUpperCase();
// 返回Promise
return new Promise((resolve,reject)=>{
// 声明xhr
const xhr = new XMLHttpRequest();
// 定义一个onreadystatechange监听事件
xhr.onreadystatechange = function () {
// 数据全部加载完成
if(xhr.readyState === 4){
// 判断状态码是否正确
if(xhr.status >= 200 && xhr.status < 300){
// 得到响应体的内容
const data = JSON.parse(xhr.responseText);
// 得到响应头
const headers = xhr.getAllResponseHeaders();
// request 即是 xhr
const request = xhr;
// 状态码
const status = xhr.status;
// 状态码的说明
const statusText = xhr.statusText
resolve({
config,
data,
headers,
request,
status,
statusText
});
}else{
reject("请求失败"+xhr.status+xhr.statusText);
}
}
}
// 判断是否拥有params,且类型为object
if(typeof config.params === "object"){
// 将object 转为 urlencoded
const arr = Object.keys(config.params);
const arr2 = arr.map(v=>v+"="+config.params[v]);
const url = arr2.join("&");
config.url += "?" + url;
}
xhr.open(method,config.url);
// post put patch
if(method === "POST" || method === "PUT" || method === "PATCH"){
if(typeof config.data === "object")
xhr.setRequestHeader("content-type","application/json");
else if(typeof config.data === "string")
xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");
xhr.send(JSON.stringify(config.data));
}else{
xhr.send();
}
})
}
以上代码实现了axios(config)
直接发起请求,例如:
axios({
method:"delete",
url:"data.json"
}).then(res=>console.log(res))
但是,如果我想通过axios.get(url[, config])
、axios.delete(url[, config])
、axios.post(url[, data[, config]])
等请求方式就行不通了。
二、封装request
通过阅读源码得到一些启示:源码中有一个名为Axios的构造函数,而我们的xhr + promise
封装在Axios.prototype.request函数中。另外我们所使用的axios.get
、axios.post
等也都是定义在Axios.prototype中。
根据这些启示将代码调整为:
// 构造函数
function Axios(){
}
Axios.prototype.request = function (config) {
// 将请求方式全部转为大写
const method = (config.method || "get").toUpperCase();
// 返回Promise
return new Promise((resolve,reject)=>{
// 声明xhr
const xhr = new XMLHttpRequest();
// 定义一个onreadystatechange监听事件
xhr.onreadystatechange = function () {
// 数据全部加载完成
if(xhr.readyState === 4){
// 判断状态码是否正确
if(xhr.status >= 200 && xhr.status < 300){
// 得到响应体的内容
const data = JSON.parse(xhr.responseText);
// 得到响应头
const headers = xhr.getAllResponseHeaders();
// request 即是 xhr
const request = xhr;
// 状态码
const status = xhr.status;
// 状态码的说明
const statusText = xhr.statusText
resolve({
config,
data,
headers,
request,
status,
statusText
});
}else{
reject("请求失败"+xhr.status+xhr.statusText);
}
}
}
// http://127.0.0.1/two?a=1&b=2
// 判断是否拥有params,且类型为object
if(typeof config.params === "object"){
// 将object 转为 urlencoded
const arr = Object.keys(config.params);
const arr2 = arr.map(v=>v+"="+config.params[v]);
const url = arr2.join("&");
config.url += "?" + url;
}
xhr.open(method,config.url);
// post put patch
if(method === "POST" || method === "PUT" || method === "PATCH"){
if(typeof config.data === "object")
xhr.setRequestHeader("content-type","application/json");
else if(typeof config.data === "string")
xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");
xhr.send(JSON.stringify(config.data));
}else{
xhr.send();
}
})
}
Axios.prototype.get = function (url,config) {
return this.request({
method:"get",
url,
...config
});
}
Axios.prototype.post = function (url,data) {
return this.request({
url,
method:"post",
data
})
}
// 其它请求delete,patch省略
export default new Axios();
这样我们终于可以通过axios.get(url[, config])
、axios.post(url[, data[, config]])
请求数据了。
import axios from "./Axios.js";
axios.post("data.json",{
a:1,
b:2
}).then(res=>{
console.log(res);
})
axios.get("data.json",{
params:{
a:1,
b:2
}
}).then(res=>{
console.log(res);
})
但是axios(config)
又不行了。
三、createInstance
函数
继续攻读源码发现:axios
本质不是Axios
构造函数的实例,而是一个函数名字为createInstance
的函数对象,在该函数中实例化了Axios
。也就是说:我们所使用的axios
并不是Axios
的实例,而是Axios.prototype.request
函数bind()
返回的函数。
function createInstance(defaultConfig){
const context = new Axios(defaultConfig);
Axios.prototype.request.bind(context);
// instance 是一个函数。该函数是request,并且将this指向context.
var instance = Axios.prototype.request.bind(context);// 等同于上面那行代码
// 将Axios的原型方法放置到instance函数属性中
Object.keys(Axios.prototype).forEach(method=>{
instance[method] = Axios.prototype[method].bind(context)
})
Object.keys(context).forEach(attr=>{
instance[attr] = context[attr];
})
return instance;
}
export default createInstance;
四、axios
实现多种请求方式原理完整代码:
// 构造函数
function Axios(){
}
Axios.prototype.request = function (config) {
// 将请求方式全部转为大写
const method = (config.method || "get").toUpperCase();
// 返回Promise
return new Promise((resolve,reject)=>{
// 声明xhr
const xhr = new XMLHttpRequest();
// 定义一个onreadystatechange监听事件
xhr.onreadystatechange = function () {
// 数据全部加载完成
if(xhr.readyState === 4){
// 判断状态码是否正确
if(xhr.status >= 200 && xhr.status < 300){
// 得到响应体的内容
const data = JSON.parse(xhr.responseText);
// 得到响应头
const headers = xhr.getAllResponseHeaders();
// request 即是 xhr
const request = xhr;
// 状态码
const status = xhr.status;
// 状态码的说明
const statusText = xhr.statusText
resolve({
config,
data,
headers,
request,
status,
statusText
});
}else{
reject("请求失败"+xhr.status+xhr.statusText);
}
}
}
// http://127.0.0.1/two?a=1&b=2
// 判断是否拥有params,且类型为object
if(typeof config.params === "object"){
// 将object 转为 urlencoded
const arr = Object.keys(config.params);
const arr2 = arr.map(v=>v+"="+config.params[v]);
const url = arr2.join("&");
config.url += "?" + url;
}
xhr.open(method,config.url);
// post put patch
if(method === "POST" || method === "PUT" || method === "PATCH"){
if(typeof config.data === "object")
xhr.setRequestHeader("content-type","application/json");
else if(typeof config.data === "string")
xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");
xhr.send(JSON.stringify(config.data));
}else{
xhr.send();
}
})
}
Axios.prototype.get = function (url,config) {
return this.request({
method:"get",
url,
...config
});
}
Axios.prototype.post = function (url,data) {
return this.request({
url,
method:"post",
data
})
}
function createInstance(defaultConfig){
const context = new Axios(defaultConfig);
Axios.prototype.request.bind(context);
// instance 是一个函数。该函数是request,并且内部this指向context.
var instance = Axios.prototype.request.bind(context);// 等同于上面那行代码
// 将Axios的原型方法放置到instance函数属性中
Object.keys(Axios.prototype).forEach(method=>{
instance[method] = Axios.prototype[method].bind(context)
})
Object.keys(context).forEach(attr=>{
instance[attr] = context[attr];
})
return instance;
}
export default createInstance;