1 前言
相信做前端的同学们,应该没有人没听说过axios的鼎鼎大名吧!
用了这么久的axios,是否也会好奇axios是如何实现的呢?
今天就让我们走进axios的源码,学习这款神级工具是如何实现的吧!
2 axios的实例与请求流程
在阅读源码之前,先大概了解一下axios实例的属性和请求整体流程,带着这些概念,阅读源码可以轻松不少!
下图是axios实例属性的简图。
可以看到axios的实例上,其实主要就这三个东西:
config:配置,比如url、method、params、headers等等
interceptors :拦截器,分为请求拦截器和返回拦截器。
request:调用xhr或者http请求的方法,参数就是config
由于调用request方法的时候可以再次传入config,但是不能传入interceptors,所以拦截器一定是要在请求之前就在axios上添加好,不能临时加。
下图是axios的请求流程,其实相当简单,先了解这个流程,看源码的时候就会有方向。
3 源码文件结构解析
axios的源码都在lib文件夹下,最核心的内容在core文件夹里。
lib
│ axios.js // 最终导出的文件
│ utils.js // 工具类
├─adapters // 适配器相关
│ adapters.js //适配器类
│ http.js // node请求
│ xhr.js // 浏览器请求
├─cancel // 取消功能相关
│ CanceledError.js //取消异常类
│ CancelToken.js //取消token类
│ isCancel.js //判断是否取消
├─core // 核心功能相关
│ Axios.js // axios类
│ AxiosError.js // axios异常类
│ AxiosHeaders.js // 请求头
│ buildFullPath.js // 构造请求地址
│ dispatchRequest.js // 发送请求方法
│ InterceptorManager.js // 拦截器的类
│ mergeConfig.js // 合并配置方法
│ settle.js // 处理请求结果方法
│ transformData.js // 数据转换执行方法
├─defaults // 默认配置
│ index.js // 默认请求参数配置
│ transitional.js // 默认transitional配置
├─env // node环境没有FormData,
│ │ data.js
│ └─classes
│ FormData.js
├─helpers // 各种工具类方法,看名字就可以大概猜到作用
│ AxiosTransformStream.js
│ AxiosURLSearchParams.js
│ bind.js
│ buildURL.js
│ callbackify.js
│ combineURLs.js
│ cookies.js
│ deprecatedMethod.js
│ formDataToJSON.js
│ formDataToStream.js
│ fromDataURI.js
│ HttpStatusCode.js
│ isAbsoluteURL.js
│ isAxiosError.js
│ isURLSameOrigin.js
│ null.js
│ parseHeaders.js
│ parseProtocol.js
│ readBlob.js
│ README.md
│ speedometer.js
│ spread.js
│ throttle.js
│ toFormData.js
│ toURLEncodedForm.js
│ validator.js
│ ZlibHeaderTransformStream.js
└─platform // 为不同环境下准备的方法
│ index.js
├─browser
│ │ index.js
│ └─classes
│ Blob.js
│ FormData.js
│ URLSearchParams.js
└─node
│ index.js
└─classes
FormData.js
URLSearchParams.js
4 源码文件阅读
4.1 入口文件 axios.js
该文件创建了一个axios实例,并且导出,所以我们import axios from 'axios'引入的就是该实例,可以直接使用,不需要再new Axios({...})这样写。
下面看一下axios实例是如何创建的吧~
// 核心方法,根据config创建axios实例
function createInstance (defaultConfig) {
// 创建axios实例
const context = new Axios(defaultConfig);
// 给Axios原型上的request方法绑定context为它的this
// 这个instance就是我们最终使用的axios
// 没想到吧,最开始的instance其实是个函数,
// 所以我们才可以使用这个用法axios('/api/url')
// 只不过后面给它扩展了很多东西
const instance = bind(Axios.prototype.request, context);
// 将Axios.prototype上的属性都绑定到instance上,
// 这样它就拥有了简写的请求方法,比如axios.get(),axios.post()
// 如果是函数,this绑定为context
utils.extend(instance, Axios.prototype, context, { allOwnKeys: true });
// 将context上的属性都绑定到instance上,
// 这样它就拥有了拦截器属性,可以使用axios.interceptors.request.use()
// 因为context上的函数的this本就指向context,所以第三个参数不需要再指定
utils.extend(instance, context, null, { allOwnKeys: true });
// 给instance增加create方法,可以通过create创建一个实例
instance.create = function create (instanceConfig) {
// 入参为拼接配置项,以instanceConfig为优先
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
// 调用上面的方法,最终导出的是axios,
// 其实是Axios.prototype.request,并扩展了很多属性
const axios = createInstance(defaults);
// 继续给axios增加属性
// 这就说明如果自己通过const myAxios=axios.create({});
// 创建出来的实例就没有下面这些属性了
// 所以下面这些属性只能通过import axios from 'axios';
// axios.all()这样的方式来使用
axios.Axios = Axios