为什么 使用axios
- 支持浏览器和node.js
- 支持promise
- 能拦截请求和响应
- 能转换请求和响应数据
- 能取消请求
- 自动转换JSON数据
- 浏览器端支持防止CSRF(跨站请求伪造)
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 # 入口文件
在接下来我们不在单独的介绍 工具函数的使用, 我们在讲解到源码的时候在逐步理解,当然我还是建议看源码之前能把工具函数都理解透才好容易理解
源码分析入口文件
作为axios项目的入口文件,我们先来看下axios.js的源码 能够实现axios的多种使用方式的核心是 createInstance 方法:
模拟 axios.js 入口文件 createInstance 的方法
-
bind 方法
主要是 apply 的 使用 可以看这篇文章
module.exports = function bind(fn, thisArg) { // 返回的是一个函数 return function wrap() { // args 其实就是 参数的集合 var args = new Array(arguments.length); for (var i = 0; i < args.length; i++) { args[i] = arguments[i]; } // 改变 this 的指向 可以理解成 fn 里边的 this 是 thisArg return fn.apply(thisArg, args); }; };
-
forEach 方法
function forEach(obj, fn) { // 如果没值 直接返回 if (obj === null || typeof obj === 'undefined') { return; } // 如果还没有可写的东西,就强制一个数组 if (typeof obj !== 'object') { obj = [obj]; } if (isArray(obj)) { // 如果是数组 for (var i = 0, l = obj.length; i < l; i++) { // 这里用到了call 参数 null 说明不改变 this 的指向 fn.call(null, obj[i], i, obj); } } else { // 如果是对象 for (var key in obj) { // 自身的属性不包含原型上的 if (Object.prototype.hasOwnProperty.call(obj, key)) { // 传参的时候相当于 把 key val 单做参数调用 fn.call(null, obj[key], key, obj); } } } }
-
extend 方法
我脑瓜子比较笨, 在这里用到了 bind 方法 我花了很长时间理解 为什么要这么写
function extend(a, b, thisArg) { // 循环 B 把 B 的 key val 进行回调的形式 赋给 A // 合并对象 把 B 的属性给 A forEach(b, function assignValue(val, key) { if (thisArg && typeof val === 'function') { // 注意 是函数的时候 调用 bind a[key] = bind(val, thisArg); } else { // 把 B 的属性给 A a[key] = val; } }); return a; }
createInstance这个方法的 倒数第二行 用到了 extend 方法 但是 extend 返回的是 Function 所以 createInstance这个方法的 return 也是一个 Function ,这个Function还会有Axios.prototype上的每个方法作为静态方法,且这些方法的上下文都是指向同一个对象。
// 首先创建 Axios 构造函数 function Axios(instanceConfig) { this.defaults = instanceConfig; // interceptors 拦截器 this.interceptors = { request: function() {}, response: function () {} }; } // 原型上的 request 方法 Axios.prototype.request = function request(config) { console.log(this) } // 原型上的 get post... 方法 Axios.prototype.get = function request(config) { console.log(this) } // 创建 function createInstance(defaultConfig) { var context = new Axios({ name: 'wang' }); } // 如果到这 我们看 request 方法中的 this 是 指向了 Axios.prototype // 但是我们想用 构造函数的 this 呢 function createInstance(defaultConfig) { var context = new Axios({ name: 'wang' }); // 在调用 bind 方法之后 request 中的 this 指向了 实例 // 而且是在执行的时候改变的 var instance = bind(Axios.prototype.request, context); } // 如果看到这里 我们应该明白为什么 使用 bind 这个函数了
function createInstance(defaultConfig) { var context = new Axios(defaultConfig); // 这一行 就是 在调用 createInstance 这个方法的时候 return 其实 Axios.prototype.request 这个方法 var instance = bind(Axios.prototype.request, context); // instance 合并 原型上的方法,而且原型上所有的方法的this 都指向 实例 utils.extend(instance, Axios.prototype, context); // 把实例上的 属性 赋值给 instance // 其实是new Axios().defaults 和 new Axios().interceptors // 也就是为什么默认配置 axios.defaults 和拦截器 axios.interceptors 可以使用的原因 utils.extend(instance, context); return instance; }
axios 核心方法 request 之拦截器
首先我们看一下这个方法都干了什么
- 设置 我们传入的 配置config
- 添加请求拦截器requestInterceptors、响应拦截器 responseInterceptors
- 发送请求 dispatchRequest
我先把 拦截器的代码注释给写一下, 如果没有明白看我下边写的小栗子 就能彻底明白拦截器
Axios.prototype.request = function request(config) {
// 判断 config 参数是否是 字符串,如果是则认为第一个参数是 URL,第二个参数是真正的config
if (typeof config === 'string') {
config = arguments[1] || {};
// 把 url 放置到 config 对象中,便于之后的 mergeConfig
config.url = arguments[0];
} else {
// 如果 config 参数是否是 字符串,则整体都当做config
config = config || {};
}
// 合并默认配置和传入的配置
config = mergeConfig(this.defaults, config);
// 设置请求方法 如果传进来有方法用传进来的
if (config.method) {
config.method = config.method.toLowerCase();
// 如果没有 用默认的
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
// 默认的也没有就用 get 方法
} else {
config.method = 'get';
}
// 创建拦截器链. dispatchRequest 是重中之重,后续重点
var chain = [dispatchRequest, undefined];
// 初始化一个promise对象,状态为resolved,接收到的参数为已经处理合并过的config对象
var promise = Promise.resolve(config);
// push各个拦截器方法 注意:interceptor.fulfilled 或 interceptor.rejected 是可能为undefined
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
// 请求拦截器逆序 注意此处的 forEach 是自定义的拦截器的forEach方法
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
// 响应拦截器顺序 注意此处的 forEach 是自定义的拦截器的forEach方法
chain.push(interceptor.fulfilled, interceptor.rejected);
});
// 循环拦截器的链
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift()); // 每一次向外弹出拦截器
}
return promise;
};
在这个方法中 用到了 InterceptorManager.js
'use strict';
var utils = require('./../utils');
// 拦截器的初始化 其实就是一组钩子函数
function InterceptorManager() {
this.handlers = [];
}
// 调用拦截器实例的use时就是往钩子函数中push方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
// 这个我也不知道干啥的
return this.handlers.length - 1;
};
// 拦截器是可以取消的,根据use的时候返回的ID,把某一个拦截器方法置为null
// 不能用 splice 或者 slice 的原因是 删除之后 id 就会变化,导致之后的顺序或者是操作不可控
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
// 这就是在 Axios的request方法中 中循环拦截器的方法 forEach 循环执行钩子函数
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
module.exports = InterceptorManager;
添加拦截器的过程
在我们项目当中 执行下边代码 说明添加了一个 请求拦截器
axios.interceptors.request.use((config) => {
// 思考 为什么 用 return
return config
}, error => {
return error
})
这个时候 会调用 InterceptorManager.js 的 use 方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
我们把添加拦截器的回调函数 保存到了 handlers 中
当我们执行 axios({ url: 'xxx' }) 调用了 Axios.prototype.request 这个方法 在这个方法当中 又调用了 InterceptorManager.js 的 forEach 方法
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
// 当前的 钩子函数 { fulfilled: fulfilled, rejected: rejected }
fn(h);
}
});
};
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 其实 到这里我们知道了 拦截器的 添加过程 ,只不过 chain 具体干了什么还不清楚
拦截器的例子
// 我们先看一下 下边的一个例子 // 执行 this.interceptors.request.forEach 是不是得到了 下边的一个数组
// 其实 chain 最后的结果是
let chain = [
// 还记得 让思考的一个问题, 为什么 在 axios.interceptors.request.use 添加拦截器的回调函数中 执行 return config 吗?
function fulfilled (config) {
// 这个方法 就是我们在执行 axios.interceptors.request.use 得第一个参数
// 这里边的代码就是 use 第一个参数的代码
config.name = 'zhang'
return config
},
function rejected () {
// 这个是 第二个参数
},
function fulfilled (config) {
// 如果不执行 都 不执行 return 大家自己写一个demo 把这段代码 执行以下
config.age = 20
return config
},
function rejected () {},
......
]
// 然后我们定义个 Promis, resolve 接收的是一个 对象, 是不是相当于 我们传入的 请求 的 data 数据
var promise = Promise.resolve({
name: 'wang',
age: 27
});
while (chain.length) {
// 思考 为什么 可以链式操作, 其实就是 拦截器回调函数 return config的作用
promise = promise.then(chain.shift(), chain.shift());
}
// Axios.prototype.request return 的 就是 promise
// 这里我们直接调用 来进行模拟
promise.then( (config) => {
console.log(config) // {name: "zhang", age: 20}
})