「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战」。
写在开头
经过上一篇 细读Axios源码系列一 - 从零搭建项目架构,项目准备、项目打包、项目测试流程 文章的学习,我们已经搭建好了 axios 的基本项目结构,相信各位小伙伴都是能轻轻松松完成的^-^,本章我们来学习 axios 的核心内容,硬货哦,要注意黑板了,喂。
话不多说,我们赶紧开始,内容有点长,请静下心来。
我们先来稍微分析分析,首先,axios 这个工具是用来发送网络请求的,那么到时肯定会涉及到 XMLHttpRequest 这个网络对象,还不了解它的小伙伴,要赶紧去学习一波了。其次,上一篇文章 中我们使用了一个正常的 axios 例子:
<script>
axios({
url: 'http://localhost:3000/posts',
}).then(res => {
console.log(res)
})
</script>
从例子中,可以看出,axios 对象最终会以函数的形式出现,函数被执行后会返回一个 Promise 对象。
axios对象的创建
围绕上面两点的分析,下面我们来看看 axios 源码中,具体是如何实现的。我们先看看 axios.js 文件的内容:
// lib/axios.js
var Axios = require('./core/Axios');
var bind = require('./helpers/bind');
function createInstance() {
// 创建 Axios 实例, 核心的一个对象
var context = new Axios();
// 把 request() 方法的 this 指向 context, 方便后面 request() 方法内部能访问到 Axios 对象,因为后续的系列方法、参数配置等都会放在 Axios 对象上
var instance = bind(Axios.prototype.request, context);
// 返回 Axios.prototype.request 方法
return instance;
}
// 创建 axios 对象
var axios = createInstance();
module.exports = axios;
上面代码看不懂?不要慌,还没全,再来看看它引入的另外两个文件,这两个是需要我们新创建的,分别是 Axios.js 和 bind.js 文件,直接跟路径创建就行了。
// lib/core/Axios.js
function Axios() {
}
// axios内部发送网络请求的核心方法, config 参数就是我们传递的请求配置信息
Axios.prototype.request = function request(config) {
console.log('核心方法-request');
// 创建一个成功状态(fulfilled)的 Promise
var promise = Promise.resolve('橙某人');
// 返回一个 Promise 对象
return promise;
}
// 其他便捷方法
Axios.prototype.get = function() {}
Axios.prototype.post = function() {}
module.exports = Axios;
下面就是关于 .bind() 方法的实现了,这就不多作介绍,前端人的必备知识点。
// lib/helpers/bind.js
module.exports = function bind(fn, thisArg) {
return function wrap() {
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
return fn.apply(thisArg, args);
};
};
创建完成后,可以稍微来测试一下我们自己写的 axios 的情况,边写边测试才不会踩太多坑。
执行打包命令 grunt build:
打包一定要是没有报错才行了哦,引入实际页面中测试:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="../dist/axios.js"></script>
</head>
<body>
<script>
axios().then(res => {
console.log(res)
})
</script>
</body>
</html>
虽然还不能发起真正的网络请求,不过也算前进了一大步,下面接着来。
request()核心方法
上面我们创建出了 axios 对象,使用 axios 对象本质就是在调用 Axios.prototype.request() 方法。接下来我们将要来实现真正的发起网络请求并返回结果,这是非常核心的一步,在 axios 源码中也是很复杂的一步,不过经过小编的一顿操作,相信你能很轻松就读懂啦,我们话不多说,立马来瞧瞧。
发起一个网络请求并返回结果,一共会涉及到四个文件,我们先来 Axios.js 文件中,继续把 request() 方法写完善:
// lib/core/Axios.js
var dispatchRequest = require('./dispatchRequest'); // 引入新文件
function Axios() {}
// axios内部发送网络请求的核心方法
Axios.prototype.request = function request(config) {
var promise;
var newConfig = config;
try {
// 把配置参数继续往下传递, 但一定会返回一个 Promise 回来
promise = dispatchRequest(newConfig);
} catch (error) {
return Promise.reject(error);
}
return promise;
}
module.exports = Axios;
继续来看第二个文件,在同级目录下创建 lib/core/dispatchRequest.js:
// lib/core/dispatchRequest.js
var defaults = require('../defaults'); // 引入新文件
module.exports = function dispatchRequest(config) {
// 获取适配器, 可以自定义适配器: https://www.axios-http.cn/docs/req_config
var adapter = config.adapter || defaults.adapter;
// 执行适配器, 实际发送起网络请求也就是这个时机
return adapter(config).then(function onAdapterResolution(response) {
return response;
}, function onAdapterRejection(reason) {
return Promise.reject(reason);
});
}
这里你可能会疑问何为 "adapter" 呢?中文直译过来叫适配器,它其实指的是一个 "环境" 中,对发起网络请求这一过程进行了一个封装。 例如,在浏览器环境中,我们会使用
XMLHttpRequest对象能发起一个网络请求;而在Node环境中,我们可以使用http或者https模块发起一个网络请求。适配器的工作就是处理网络请求过程中的详情步骤过程,并最终返回结果。 不知道这么解释你是否能稍微理解了?(◕ᴗ◕✿)
接着继续创建第三个文件 lib/defaults.js ,它是整个 axios 默认的配置信息文件。
// lib/defaults.js
// 根据环境返回合适的适配器
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// 浏览器环境, 浏览器发起网络请求是使用了 XMLHttpRequest 对象
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// Node 环境, node 发起网络请求是使用了 http 模块
// adapter = require('./adapters/http');
}
return adapter;
}
var defaults = {
timeout: 0,
adapter: getDefaultAdapter(),
}
module.exports = defaults;
发起网络请求 - xhr.js
我们来创建最后的适配器文件 - lib/adapters/xhr.js,这里我们先只考虑浏览器环境的情况。
// lib/adapters/xhr.js
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
// 创建 XMLHttpRequest 对象
var request = new XMLHttpRequest();
// 初始化请求
request.open(config.method.toUpperCase(), config.url, true);
request.onreadystatechange = function handleLoad() {
// 0-请求未初始化; 1-服务器连接已建立; 2-请求已接收; 3-请求处理中; 4-请求已完成, 且响应已就绪
if (!request || request.readyState !== 4) return;
resolve(request.response);
};
request.send();
});
}
上面代码实际就是 XMLHttpRequest 对象的基础使用,它是 Javascript 语言中最基础也是最重要的知识点之一,这本应该是每个前端人的必会知识,奈何现在是各种"上层"封装,它磨灭了大部分人对"底层"基础的学习。希望对它还不了解的小伙伴们,赶紧学习起来哦,加油卷起来。
做完这些后,我们就又到了测试环节的时候了,老样子,执行打包命令:grunt build。
打包不要有报错哦,然后我们引入自己项目写的 axios 进行测试:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="../dist/axios.js"></script>
</head>
<body>
<script>
axios({
url: 'http://localhost:3000/posts',
method: 'get'
}).then(res => {
console.log(res)
})
</script>
</body>
</html>
能正常拿到结果,就说明你大功告成了。
最后,贴一下最新的项目目录结果:
文件名称、文件目录其实你也可以按你自己的想法创建,我的项目结构完全是依照 axios 源码本身来创建的,为的就是让你下次直接去阅读 axios 源码完全没压力,因为你本身都写过一遍,理解过每个细节,也就不存在看不懂的情况了吧。你说是吧?嘿嘿!
最后,我们画个图,分析一下整个网络请求过程的一个流程:
实线框是调用经过的函数,虚线框是每个函数会做的事情,最上面是函数所在的文件名称。
其实,需要特别注意的是返回的结果是通过一条 Promise 链在做传递的,这可能会稍微让你有点"迷",希望上面的图能让你理解清晰一点,当然,最好你也自己画一画唷。
那么,这一章就先到这里了,后面的内容更精彩哦,先预告下一节的核心 - "拦截器"。
至此,本篇文章就写完啦,撒花撒花。
希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。