/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 对象创建过程可以看下面代码以及代码注释。
<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.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 基础中提到过一个拦截器顺序的示例,当时执行结果是先执行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
对象和发送请求可以看上面的代码,这里有些简略,只是搭了个框架。
<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>
整体流程:request(config)
=> dispatchRequest(config)
=> xhrAdapter(config)
request(config)
:将请求拦截器 / dispatchRequest()
/ 响应拦截器 通过 promise
链串连起来,返回 promise
xhrAdapter()
发请求 => 请求返回后转换响应数据,返回promise
xhrAdapter(config)
:创建 XHR
对象,根据 config
进行相应设置,发送特定请求,并接收响应数据,返回 promise
至此,axios
源码模拟实现就结束了,我是看了尚硅谷的视频进行学习的,感觉模拟实现的代码中核心思想还是有的,不过想要更深入了解最好还是从头认认真真的研究一下源码。