├── /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 # 入口文件
复制代码
在接下来咱们不在单独的介绍 工具函数的使用, 咱们在讲解到源码的时候在逐步理解,固然我仍是建议看源码以前能把工具函数都理解透才好容易理解javascript
做为axios项目的入口文件,咱们先来看下axios.js的源码 可以实现axios的多种使用方式的核心是 createInstance 方法:java
bind 方法node
主要是 apply 的 使用 能够看这篇文章ios
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 方法json
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 方法axios
我脑瓜子比较笨, 在这里用到了 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上的每一个方法做为静态方法,且这些方法的上下文都是指向同一个对象。promise
// 首先建立 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;
}
复制代码
首先咱们看一下这个方法都干了什么浏览器
我先把 拦截器的代码注释给写一下, 若是没有明白看我下边写的小栗子 就能完全明白拦截器app
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}
})
复制代码