一、分析文件结构
/dist/
输出目录,最后输出的axios
整体文件axios.js
未压缩的axios.min.js
压缩后的
/lib/
源码目录,所有的源代码都放在这里/adapters/
请求的适配器http.js
实现http
适配器(包装http
包),在node.js
向远程服务器发送请求时使用xhr.js
实现xhr
适配器(包装xhr
对象),在浏览器端发送请求时使用
/cancel/
定义取消功能Cancel.js
构造函数,用来创建取消时的错误对象CancelToken.js
取消请求的构造函数isCancel.js
检测参数是否为取消对象
/core/
一些核心功能Axios.js
axios
的核心主类,文件存放的是axios
的构造函数buildFullPath.js
构建完整 URL 的函数文件createError.js
创建指定信息的 Error 对象dispatchRequest.js
发送请求的函数文件enhanceError.js
更新错误对象的函数文件InterceptorManager.js
拦截器的管理器mergeConfig.js
合并配置的函数文件settle.js
根据http
响应状态,改变Promise
的状态transformData.js
数据格式转化函数
/helpers/
一些功能函数bind.js
返回一个新的函数,并将新函数this
绑定到一个对象身上buildURL.js
创建一个URL
,将参数缀到URL
后,并返回格式化后的内容combineURLs.js
合并URL
cookies.js
处理cookie
deprecatedMethod.js
控制台提示不赞成使用的方法isAbsoluteURL.js
检测是否为绝对路径的 URLisURLSameOrigin.js
检测是否为同源的 URLnormalizeHeaderName.js
统一化头信息, 统一变为大写parseHeaders.js
解析将头信息解析为对象spread.js
用于调用函数和扩展参数数组的语法糖
-axios.js
axios
入口文件
defaults.js
axios
配置文件utils.js
工具函数文件
package.json
包信息index.d.ts
配置TypeScript
的声明文件index.js
整个包的入口文件
二、模拟实现 axios 对象创建过程
模拟实现 axios 对象创建过程可以看下面代码以及代码注释。
<script>
// 构造函数
function Axios(config){
// 初始化
this.defaults = config;// 为了创建 default 默认属性
this.interceptors = {
request: {},
response: {}
}
}
// 原型添加相关的方法
Axios.prototype.request = function(config){
console.log('发送请求,请求的类型为 '+ config.method);
}
Axios.prototype.get = function(config){
return this.request({method: 'GET'});
}
Axios.prototype.post = function(config){
return this.request({method: 'POST'});
}
// 声明函数
function createInstance(config){
// 实例化一个对象
// 可以 context.get() context.post() 这样使用,但是不能当做函数使用,即不能 context() 这样使用
let context = new Axios(config);
// 创建请求函数
// 此时 instance 是一个函数,并且可以调用instance()然后往里面传对象,比如instance({})。
// 此时 instance 不能 instance.get 这样使用。
let instance = Axios.prototype.request.bind(context);
// 为了可以 instance.get 这样用,将 Axios.prototype 对象中的方法添加到 instance 函数对象中
// 为了保证函数在调用时 this 一定指向实例对象 context,更加严谨些,加个bind()
Object.keys(Axios.prototype).forEach(key => {
instance[key] = Axios.prototype[key].bind(context);
});
// 为 instance 这个函数对象添加 default 和 interceptors 属性
Object.keys(context).forEach(key => {
instance[key] = context[key];
});
return instance;
}
let axios = createInstance();
// 发送请求
// axios({method:'POST'});
axios.get({});
axios.post({});
</script>
三、模拟实现 axios 发送请求的过程
axios
是由Axios.prototype.request
这个函数通过bind
创建而来的,所以无论是 axios()
还是axios.get()
、axios.post()
等方式,请求的源头都是request
。
模拟实现 axios 发送请求的过程可以看下面代码以及代码注释。
<script>
// axios 发送请求
//1. 声明构造函数
function Axios(config){
this.config = config;
}
Axios.prototype.request = function(config){
//发送请求
//源码中在创建前做了合并处理等
//创建一个 promise 对象,可以看到这个 promise 一定是成功的
let promise = Promise.resolve(config);
//声明一个数组
let chains = [dispatchRequest, undefined];// 这里的 undefined 是一个占位
//调用 then 方法指定回调
//这里的 then 方法执行后会执行下面的 dispatchRequest 函数,函数的返回结果是由适配器 xhrAdapter 执行结果决定的。
//成功时候,这里 result 结果值就是下面的 response
let result = promise.then(chains[0], chains[1]);
//返回 promise 的结果
//这里的 result 就是 request 函数的执行结果,也就是 axios() 的执行结果
return result;
}
//2. dispatchRequest 函数
function dispatchRequest(config){
// 可以在这里进行对请求数据进行初始化转化、合并一切其他头信息的配置项等处理(源码中进行了处理)
//调用适配器发送请求
return xhrAdapter(config).then(response => {
//可以在这里对响应的结果进行转换处理(源码中进行了处理)
//这里的 response 就是 上面的 result 成功时候的结果值。
return response;
}, error => {
throw error;
});
}
//3. adapter 适配器
function xhrAdapter(config){
console.log('xhrAdapter 函数执行');
return new Promise((resolve, reject) => {
//发送 AJAX 请求
let xhr = new XMLHttpRequest();
//初始化
xhr.open(config.method, config.url);
//发送
xhr.send();
//绑定事件
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
//判断成功的条件
if(xhr.status >= 200 && xhr.status < 300){
//成功的状态
resolve({
//配置对象
config: config,
//响应体
data: xhr.response,
//响应头
headers: xhr.getAllResponseHeaders(), //是一个字符串,源码中是做了格式化,用 parseHeaders 将头信息解析成对象
// xhr 请求对象
request: xhr,
//响应状态码
status: xhr.status,
//响应状态字符串
statusText: xhr.statusText
});
}else{
//失败的状态
reject(new Error('请求失败,失败的状态码为' + xhr.status));
}
}
}
});
}
//4. 创建 axios 函数
let axios = Axios.prototype.request.bind(null);
//这里就是最上面 request 函数的执行结果 result
//然后执行.then 操作
axios({
method:'GET',
url:'http://localhost:3000/posts'
}).then(response => {
console.log(response);
});
</script>
四、模拟实现 axios 拦截器功能
在我之前的博客一文掌握 axios 基础中提到过一个拦截器顺序的示例,当时执行结果是先执行2号请求拦截器,后执行1号请求拦截器,当时有解释原因。
原因如下:
promise
在遍历执行时,使用的是数组的shift
方法每次从中取出两个函数(成功回调,失败回调)执行。
而在遍历执行前,我们要先将拦截器的请求回调和响应回调都压入一个数组中,之后再进行遍历运行。
在向数组中添加 请求拦截器函数 时,根据axios
请求的执行顺序,请求拦截器 应该在发送请求之前执行,所以应该添加在 发送请求函数 的前面,因此使用的是数组的unshift
方法,即头部添加,故后面添加的 请求拦截器 总是放在头部。
故 请求拦截器2 先 请求拦截器1 后。
那么源码中 响应拦截器是使用的什么方法呢?它使用的是数组的push
方法。
在向数组中添加 请求拦截器函数 时,根据axios
请求的执行顺序,响应拦截器 应该在发送请求之后执行,所以应该添加在 发送请求函数 的后面,因此使用的是数组的push
方法,即尾部添加,故后面添加的 响应拦截器 总是放在尾部。
故 响应拦截器1 先 响应拦截器2 后。
现在来模拟实现 axios 拦截器功能,具体可以看下面代码以及代码注释。
其中创建axios
对象和发送请求可以看上面的代码,这里有些简略,只是搭了个框架。
<script>
//1.构造函数
function Axios(config){
this.config = config;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
}
}
//3.发送请求 难点与重点
Axios.prototype.request = function(config){
//源码中在创建前做了合并处理等
//创建一个 promise 对象
let promise = Promise.resolve(config);
//创建一个数组
const chains = [dispatchRequest, undefined];// 这里的 undefined 是一个占位
//处理拦截器
//请求拦截器
//将请求拦截器的回调 压入到 chains 的前面
this.interceptors.request.handlers.forEach(item => {
chains.unshift(item.fulfilled, item.rejected);
});
//响应拦截器
this.interceptors.response.handlers.forEach(item => {
chains.push(item.fulfilled, item.rejected);
});
// console.log(chains);
//promise 遍历执行,使用数组的 shift 方法每次从中取出两个函数(成功回调,失败回调)执行
while(chains.length > 0){
promise = promise.then(chains.shift(), chains.shift());
}
return promise;
}
//发送请求
function dispatchRequest(config){
//返回一个promise
return new Promise((resolve, reject) => {
resolve({
status: 200,
statusText: 'OK'
});
});
}
//2.拦截器管理器构造函数
function InterceptorManager(){
this.handlers = [];
}
//一旦调用use,就把成功和失败的回调函数做成一个对象压入到 handlers 中
InterceptorManager.prototype.use = function(fulfilled, rejected){
this.handlers.push({
fulfilled,
rejected
})
}
//4.创建实例
let context = new Axios({});
//创建axios函数
let axios = Axios.prototype.request.bind(context);
//为 axios 这个函数对象添加 context 的 default 与 interceptors 属性
Object.keys(context).forEach(key => {
axios[key] = context[key];
});
//5.以下为功能测试代码
// 设置请求拦截器 config 配置对象
axios.interceptors.request.use(function one(config) {
console.log('请求拦截器 成功 - 1号');
return config;
}, function one(error) {
console.log('请求拦截器 失败 - 1号');
return Promise.reject(error);
});
axios.interceptors.request.use(function two(config) {
console.log('请求拦截器 成功 - 2号');
return config;
}, function two(error) {
console.log('请求拦截器 失败 - 2号');
return Promise.reject(error);
});
// 设置响应拦截器
axios.interceptors.response.use(function (response) {
console.log('响应拦截器 成功 1号');
return response;
}, function (error) {
console.log('响应拦截器 失败 1号')
return Promise.reject(error);
});
axios.interceptors.response.use(function (response) {
console.log('响应拦截器 成功 2号')
return response;
}, function (error) {
console.log('响应拦截器 失败 2号')
return Promise.reject(error);
});
//6.发送请求
axios({
method: 'GET',
url: 'http://localhost:3000/posts'
}).then(response => {
console.log(response);
});
</script>
五、模拟实现 axios 取消请求功能
现在来模拟实现 axios 取消请求功能,具体可以看下面代码以及代码注释。
其中创建axios
对象和发送请求可以看上面的代码,这里有些简略,只是搭了个框架。
<body>
<div class="container">
<h2 class="page-header">axios取消请求</h2>
<button class="btn btn-primary"> 发送请求 </button>
</div>
<script>
//构造函数
function Axios(config){
this.config = config;
}
//原型 request 方法
Axios.prototype.request = function(config){
return dispatchRequest(config);
}
//dispatchRequest 函数
function dispatchRequest(config){
return xhrAdapter(config);
}
//xhrAdapter
function xhrAdapter(config){
//发送 AJAX 请求
return new Promise((resolve, reject) => {
//实例化对象
const xhr = new XMLHttpRequest();
//初始化
xhr.open(config.method, config.url);
//发送
xhr.send();
//处理结果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
//判断结果
if(xhr.status >= 200 && xhr.status < 300){
//设置为成功的状态
resolve({
status: xhr.status,
statusText: xhr.statusText
});
}else{
reject(new Error('请求失败'));
}
}
}
//关于取消请求的处理
if(config.cancelToken){
//对 cancelToken 对象身上的 promise 对象指定成功的回调
config.cancelToken.promise.then(value => {
// 取消请求
xhr.abort();
//将整体结果设置为失败
reject(new Error('请求已经被取消'))
});
}
})
}
// 创建 axios 函数
const context = new Axios({});
const axios = Axios.prototype.request.bind(context);
// CancelToken 构造函数
function CancelToken(executor){
// 声明一个变量
var resolvePromise;
// 为实例对象添加属性,这个 promise 属性的值是一个 Promise 对象,并且初始是 pedding 状态
this.promise = new Promise((resolve) => {
// 将 resolve 赋值给 resolvePromise
// 这样执行 resolvePromise 函数时,就能更改 promise 原状态
//这是因为,resolve 和 resolvePromise 都是引用类型,指向的是同一个内存地址,resolvePromise执行,则 resolve 也执行
resolvePromise = resolve
});
// 调用 executor 函数
executor(function(){
// 执行 resolvePromise 函数
resolvePromise();
});
}
// 获取按钮 以上为模拟实现的代码
const btns = document.querySelectorAll('button');
//声明全局变量
let cancel = null;
//发送请求
btns[0].onclick = function(){
//检测上一次的请求是否已经完成
if(cancel !== null){
//取消上一次的请求
cancel();
}
//创建 cancelToken 的值
let cancelToken = new CancelToken(function(c){
// 此时的 function(c){} 就是上面的CancelToken构造函数里的 executor,
// 所以此时的 c 就是上面的CancelToken构造函数里的 function(){resolvePromise()}
// 所以这里的 cancel 其实就是 function(){resolvePromise()}
// cancel 一旦执行,resolvePromise() 就会执行,CancelToken构造函数里的 promise 属性的状态就会改变
cancel = c;
});
axios({
method: 'GET',
url: 'http://localhost:3000/posts',
//添加配置对象的属性
cancelToken: cancelToken
}).then(response => {
console.log(response);
//将 cancel 的值初始化
cancel = null;
})
}
</script>
</body>
六、axios运行的整体流程
整体流程:request(config)
=> dispatchRequest(config)
=> xhrAdapter(config)
request(config)
:将请求拦截器 /dispatchRequest()
/ 响应拦截器 通过promise
链串连起来,返回promise
- dispatchRequest(config):转换请求数据 => 调用
xhrAdapter()
发请求 => 请求返回后转换响应数据,返回promise
xhrAdapter(config)
:创建XHR
对象,根据config
进行相应设置,发送特定请求,并接收响应数据,返回promise
至此,axios
源码模拟实现就结束了,我是看了尚硅谷的视频进行学习的,感觉模拟实现的代码中核心思想还是有的,不过想要更深入了解最好还是从头认认真真的研究一下源码。