文章源码托管在github上,欢迎fork指正!
axios 是一个基于 Promise 的http请求库,可以用在浏览器和node.js中,目前在github上有 42K 的star数
备注
- 每一小节都会从两个方面介绍:如何使用 -> 源码分析
- [工具方法简单介绍]一节可先跳过,后面用到了再过来查看
- axios最核心的技术点是如何拦截请求响应并修改请求参数修改响应数据 和 axios是如何用promise搭起基于xhr的异步桥梁的
axios项目目录结构
├── /dist/ # 项目输出目录
├── /lib/ # 项目源码目录
│ ├── /cancel/ # 定义取消功能
│ ├── /core/ # 一些核心功能
│ │ ├── Axios.js # axios的核心主类
│ │ ├── dispatchRequest.js # 用来调用http请求适配器方法发送请求
│ │ ├── InterceptorManager.js # 拦截器构造函数
│ │ └── settle.js # 根据http响应状态,改变Promise的状态
│ ├── /helpers/ # 一些辅助方法
│ ├── /adapters/ # 定义请求的适配器 xhr、http
│ │ ├── http.js # 实现http适配器
│ │ └── xhr.js # 实现xhr适配器
│ ├── axios.js # 对外暴露接口
│ ├── defaults.js # 默认配置
│ └── utils.js # 公用工具
├── package.json # 项目信息
├── index.d.ts # 配置TypeScript的声明文件
└── index.js # 入口文件
注:因为我们需要要看的代码都是/lib/
目录下的文件,所以以下所有涉及到文件路径的地方,
我们都会在/lib/
下进行查找
名词解释
-
拦截器 interceptors
(如果你熟悉中间件,那么就很好理解了,因为它起到的就是基于promise的中间件的作用)
拦截器分为请求拦截器和响应拦截器,顾名思义:
请求拦截器(interceptors.request
)是指可以拦截住每次或指定http请求,并可修改配置项
响应拦截器(interceptors.response
)可以在每次http请求后拦截住每次或指定http请求,并可修改返回结果项。这里先简单说明,后面会做详细的介绍如何拦截请求响应并修改请求参数修改响应数据。
-
数据转换器 (其实就是对数据进行转换,比如将对象转换为JSON字符串)
数据转换器分为请求转换器和响应转换器,顾名思义:
请求转换器(transformRequest
)是指在请求前对数据进行转换,
响应转换器(transformResponse
)主要对请求响应后的响应体做数据转换。 -
http请求适配器(其实就是一个方法)
在axios项目里,http请求适配器主要指两种:XHR、http。
XHR的核心是浏览器端的XMLHttpRequest对象,
http核心是node的http[s].request方法当然,axios也留给了用户通过config自行配置适配器的接口的,
不过,一般情况下,这两种适配器就能够满足从浏览器端向服务端发请求或者从node的http客户端向服务端发请求的需求。本次分享主要围绕XHR。
-
config配置项 (其实就是一个对象)
此处我们说的config,在项目内不是真的都叫config这个变量名,这个名字是我根据它的用途起的一个名字,方便大家理解。
在axios项目中的,设置\读取config时,
有的地方叫它defaults
(/lib/defaults.js
),这儿是默认配置项,
有的地方叫它config
,如Axios.prototype.request
的参数,再如xhrAdapter
http请求适配器方法的参数。config在axios项目里的是非常重要的一条链,是用户跟axios项目内部“通信”的主要桥梁。
axios内部的运作流程图
工具方法简单介绍
(注:本节可先跳过,后面用到了再过来查看)
有一些方法在项目中多处使用,简单介绍下这些方法
- bind: 给某个函数指定上下文,也就是this指向
bind(fn, context);
实现效果同Function.prototype.bind
方法: fn.bind(context)
- forEach:遍历数组或对象
var utils = require('./utils');
var forEach = utils.forEach;
// 数组
utils.forEach([], (value, index, array) => {
})
// 对象
utils.forEach({
}, (value, key, object) => {
})
- merge:深度合并多个对象为一个对象
var utils = require('./utils');
var merge = utils.merge;
var obj1 = {
a: 1,
b: {
bb: 11,
bbb: 111,
}
};
var obj2 = {
a: 2,
b: {
bb: 22,
}
};
var mergedObj = merge(obj1, obj2);
mergedObj对象是:
{
a: 2,
b: {
bb: 22,
bbb: 111
}
}
- extend:将一个对象的方法和属性扩展到另外一个对象上,并指定上下文
var utils = require('./utils');
var extend = utils.extend;
var context = {
a: 4,
};
var target = {
k: 'k1',
fn(){
console.log(this.a + 1)
}
};
var source = {
k: 'k2',
fn(){
console.log(this.a - 1)
}
};
let extendObj = extend(target, source, context);
extendObj对象是:
{
k: 'k2',
fn: source.fn.bind(context),
}
执行extendObj.fn();
, 打印3
axios为何会有多种使用方式
如何使用
// 首先将axios包引进来
import axios from 'axios'
第1种使用方式:axios(option)
axios({
url,
method,
headers,
})
第2种使用方式:axios(url[, option])
axios(url, {
method,
headers,
})
第3种使用方式(对于get、delete
等方法):axios[method](url[, option])
axios.get(url, {
headers,
})
第4种使用方式(对于post、put
等方法):axios[method](url[, data[, option]])
axios.post(url, data, {
headers,
})
第5种使用方式:axios.request(option)
axios.request({
url,
method,
headers,
})
源码分析
作为axios项目的入口文件,我们先来看下axios.js
的源码
能够实现axios的多种使用方式的核心是createInstance
方法:
// /lib/axios.js
function createInstance(defaultConfig) {
// 创建一个Axios实例
var context = new Axios(defaultConfig);
// 以下代码也可以这样实现:var instance = Axios.prototype.request.bind(context);
// 这样instance就指向了request方法,且上下文指向context,所以可以直接以 instance(option) 方式调用
// Axios.prototype.request 内对第一个参数的数据类型判断,使我们能够以 instance(url, option) 方式调用
var instance = bind(Axios.prototype.request, context);
// 把Axios.prototype上的方法扩展到instance对象上,
// 这样 instance 就有了 get、post、put等方法
// 并指定上下文为context,这样执行Axios原型链上的方法时,this会指向context
utils.extend(instance, Axios.prototype, context);
// 把context对象上的自身属性和方法扩展到instance上
// 注:因为extend内部使用的forEach方法对对象做for in 遍历时,只遍历对象本身的属性,而不会遍历原型链上的属性
// 这样,instance 就有了 defaults、interceptors 属性。(这两个属性后面我们会介绍)
utils.extend(instance, context);
return instance;
}
// 接收默认配置项作为参数(后面会介绍配置项),创建一个Axios实例,最终会被作为对象导出
var axios = createInstance(defaults);
以上代码看上去很绕,其实createInstance
最终是希望拿到一个Function,这个Function指向Axios.prototype.request
,这个Function还会有Axios.prototype
上的每个方法作为静态方法,且这些方法的上下文都是指向同一个对象。
那么在来看看Axios、Axios.prototype.request
的源码是怎样的?
Axios
是axios包的核心,一个Axios
实例就是一个axios应用,其他方法都是对Axios
内容的扩展
而Axios
构造函数的核心方法是request
方法,各种axios的调用方式最终都是通过request
方法发请求的
// /lib/core/Axios.js
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
Axios.prototype.request = function request(config) {
// ...省略代码
};
// 为支持的请求方法提供别名
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {
}, {
method: method,
url: url
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {
}, {
method: method,
url: url,
data: data
}));
};</