Axios是一个基于Promise的HTTP请求库,可以用在浏览器和Node.js中。平时在Vue项目中,经常使用它来实现HTTP请求。
它的使用简便、灵活,并且有interceptors、数据转换器等强大的功能,以前用的时候并没有仔细研究过这些功能是如何实现的,正好在知乎的大前端专栏看到一篇文章对Axios的源码进行了解读。借着这篇文章的帮助,我开始了自己阅读源码的道路。
以后要多多的读源码,更多的独立完成,向大神们学习。
Axios的目录结构
Axios的目录结构相对还是比较简单的
目录里面adapters/
目录下定义的是如何发出一个HTTP请求,这也就是为什么Axios技能应用在浏览器中(XHR)又能用在Node.js中(http.request
),core/Axios.js
是Axios的核心主类,axios.js
是整个Axios的入口。
Axios的实现流程
graph TB
引入axios-->Axios构造函数实例化
Axios构造函数实例化-->Interceptors请求拦截器
Interceptors请求拦截器-->dispatchRequest方法
dispatchRequest方法-->请求转换器transformRequest
请求转换器transformRequest-->http请求适配器adapter
http请求适配器adapter-->响应转换器transformResponse
响应转换器transformResponse-->Interceptors响应拦截器
工具函数的学习
forEach
这个forEach
与原生的数组的forEach
并不相同,它可以遍历对象,也可以遍历数组,还可以遍历基本值:
function forEach(obj, fn) {
// 如果是空值就返回
if (obj === null || typeof obj === 'undefined') {
return;
}
// 如果是基本类型,则放到数组里面进行遍历
if (typeof obj !== 'object') {
/*eslint no-param-reassign:0*/
obj = [obj];
}
if (isArray(obj)) {
// 遍历数组
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
// 遍历对象
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj);
}
}
}
}
merge
和deepMerge
用来合并对象,二者的区别只是对于嵌套的深层的对象,deepMerge
也会进行深层的拷贝,而不是指针的改变
function merge(/* obj1, obj2, obj3, ... */) {
var result = {};
function assignValue(val, key) {
if (typeof result[key] === 'object' && typeof val === 'object') {
result[key] = merge(result[key], val);
} else {
result[key] = val;
}
}
for (var i = 0, l = arguments.length; i < l; i++) {
forEach(arguments[i], assignValue);
}
return result;
}
function deepMerge(/* obj1, obj2, obj3, ... */) {
var result = {};
function assignValue(val, key) {
if (typeof result[key] === 'object' && typeof val === 'object') {
result[key] = deepMerge(result[key], val);
} else if (typeof val === 'object') {
result[key] = deepMerge({}, val);
} else {
result[key] = val;
}
}
for (var i = 0, l = arguments.length; i < l; i++) {
forEach(arguments[i], assignValue);
}
return result;
}
isStandardBrowserEnv
用来判断是否是标准的浏览器环境,对于Web Workers,
typeof window -> undefined
typeof document -> undefined
对于RN和NativeScript
react-native:
navigator.product -> 'ReactNative'
nativescript
navigator.product -> 'NativeScript' or 'NS'
所以有:
/**
* Determine if we're running in a standard browser environment
*
* This allows axios to run in a web worker, and react-native.
* Both environments support XMLHttpRequest, but not fully standard globals.
*
* web workers:
* typeof window -> undefined
* typeof document -> undefined
*
* react-native:
* navigator.product -> 'ReactNative'
* nativescript
* navigator.product -> 'NativeScript' or 'NS'
*/
function isStandardBrowserEnv() {
if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' ||
navigator.product === 'NativeScript' ||
navigator.product === 'NS')) {
return false;
}
return (
typeof window !== 'undefined' &&
typeof document !== 'undefined'
);
}
Axios的多种使用方式
Axios有多种使用方式:
import axios from 'axios';
// 第一种 axios(option)
axios({ url, method, headers });
// 第二种 axios(url[, option]);
axios(url, { method, headers })
// 第三种(对于 get/delete 等方法) axios.[method](url[, option])
axios.get(url, { headers })
// 第四种(对于 post/put等方法)axios[.method](url[, data[, option]])
axios.post(url, data, { headers })
// 第五种 axios.request(option)
axios.request({ url, method, headers })
下面从入口文件axios.js
来分析这些使用方式都是如何实现的
// axios.js
// 用来创建一个 axios 的实例
function createInstance(defaultConfig) {
// 通过默认配置新建一个 axios 实例
var context = new Axios(defaultConfig);
// 通过 bind 方法获取到 instance,并且绑定 this 上下文
// instance 是一个函数,实际上就是 Axios.prototype.request.bind(context),其上下文指向 context
// 所以可以通过 instance(options)的方法调用
var instance = bind(Axios.prototype.request, context);
// 将 Axios 原型上的属性和方法复制到 instance 上,作为静态属性和静态方法
// Axios.prototype 上定义了 get/delete/post 等方法,所以可以直接使用 instance.get 这种形式调用
utils.extend(instance, Axios.prototype, context);
// 将 context 实例的属性和方法复制到 instance 上,作为静态属性和静态方法
// context.request 指向 Axios.prototype.request,所以可以通过instance.request 这种形式调用
utils.extend(instance, context);
// 返回 request 方法,它与 context 的差别仅仅在于它本身是一个函数,可以直接调用
return instance;
}
// 接受默认配置项作为参数,创建一个 request 方法,具有 Axios 的各种实例属性、方法以及原型属性和方法
// 可以认为导出的就是 Axios 的实例
var axios = createInstance(defaults);
// 暴露 Axios 类,用于继承
axios.Axios = Axios;
// 实例上定义的 cre