当前位置:   article > 正文

实习打怪之路:axios的底层原理(我一脸懵)_axios底层

axios底层

目录

一、axios简介

axios是什么?

axios有什么特性?(不得不说面试被问到几次)

二、基本使用方式

三、实现axios和axios.method

四、请求和响应拦截器


一、axios简介

axios是什么?

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

axios有什么特性?(不得不说面试被问到几次)

1.从浏览器中创建 XMLHttpRequests
2.从 node.js 创建 http 请求
3.支持 Promise API
4.拦截请求和响应
5.转换请求数据和响应数据
6.取消请求
7.自动转换 JSON 数据
8.客户端支持防御 XSRF

实际上,axios可以用在浏览器和 node.js 中是因为,它会自动判断当前环境是什么,如果是浏览器,就会基于XMLHttpRequests实现axios。如果是node.js环境,就会基于node内置核心模块http实现axios

简单来说,axios的基本原理就是

        1.axios还是属于 XMLHttpRequest, 因此需要实现一个ajax。或者基于http 。

        2.还需要一个promise对象来对结果进行处理

二、基本使用方式

axios基本使用方式主要有

        1.axios(config)

        2.axios.method(url, data , config)

  1. // index.html文件
  2. <html>
  3. <script type="text/javascript" src="axios"></script>
  4. <body>
  5. <button class="btn">点我发送请求</button>
  6. <script>
  7. document.querySelector('.btn').onclick = function() {
  8. // 分别使用以下方法调用,查看myaxios的效果
  9. axios.post('/postAxios', {
  10. name: '小美post'
  11. }).then(res => {
  12. console.log('postAxios 成功响应', res);
  13. })
  14. axios({
  15. method: 'post',
  16. url: '/getAxios'
  17. }).then(res => {
  18. console.log('getAxios 成功响应', res);
  19. })
  20. }
  21. </script>
  22. </body>
  23. </html>
  24. </html>

三、实现axios和axios.method

从axios(config)的使用上可以看出导出的axios是一个方法。从axios.method(url, data , config)的使用可以看出导出的axios上或者原型上挂有get,post等方法

实际上导出的axios就是一个Axios类中的一个方法。

如代码所以,核心代码是request。我们把request导出,就可以使用axios(config)这种形式来调用axios了。
 

  1. class Axios {
  2. constructor() {
  3. }
  4. request(config) {
  5. return new Promise(resolve => {
  6. const {url = '', method = 'get', data = {}} = config;
  7. // 发送ajax请求
  8. const xhr = new XMLHttpRequest();
  9. xhr.open(method, url, true);
  10. xhr.onload = function() {
  11. console.log(xhr.responseText)
  12. resolve(xhr.responseText);
  13. }
  14. xhr.send(data);
  15. })
  16. }
  17. }

怎么导出呢?十分简单,new Axios,获得axios实例,再获得实例上的request方法就好了。

  1. // 最终导出axios的方法,即实例的request方法
  2. function CreateAxiosFn() {
  3. let axios = new Axios();
  4. let req = axios.request.bind(axios);
  5. return req;
  6. }
  7. // 得到最后的全局变量axios
  8. let axios = CreateAxiosFn();

点击查看此时的myAxios.js

现在axios实际上就是request方法。

你可能会很疑惑,因为我当初看源码的时候也很疑惑:干嘛不直接写个request方法,然后导出呢?非得这样绕这么大的弯子。别急。后面慢慢就会讲到。

现在一个简单的axios就完成了,我们来引入myAxios.js文件并测试一下可以使用不?

简单的搭建服务器

  1. //server.js
  2. var express = require('express');
  3. var app = express();
  4. //设置允许跨域访问该服务.
  5. app.all('*', function (req, res, next) {
  6. res.header('Access-Control-Allow-Origin', '*');
  7. res.header('Access-Control-Allow-Headers', 'Content-Type');
  8. res.header('Access-Control-Allow-Methods', '*');
  9. res.header('Content-Type', 'application/json;charset=utf-8');
  10. next();
  11. });
  12. app.get('/getTest', function(request, response){
  13. data = {
  14. 'FrontEnd':'前端',
  15. 'Sunny':'阳光'
  16. };
  17. response.json(data);
  18. });
  19. var server = app.listen(5000, function(){
  20. console.log("服务器启动");
  21. });
  1. //index.html
  2. <script type="text/javascript" src="./myAxios.js"></script>
  3. <body>
  4. <button class="btn">点我发送请求</button>
  5. <script>
  6. document.querySelector('.btn').onclick = function() {
  7. // 分别使用以下方法调用,查看myaxios的效果
  8. axios({
  9. method: 'get',
  10. url: 'http://localhost:5000/getTest'
  11. }).then(res => {
  12. console.log('getAxios 成功响应', res);
  13. })
  14. }
  15. </script>
  16. </body>

点击按钮,看看是否能成功获得数据。

可喜可贺,成功。

现在我们来实现下axios.method()的形式。

思路。我们可以再Axios.prototype添加这些方法。而这些方法内部调用request方法即可,如代码所示:

  1. // 定义get,post...方法,挂在到Axios原型上
  2. const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];
  3. methodsArr.forEach(met => {
  4. Axios.prototype[met] = function() {
  5. console.log('执行'+met+'方法');
  6. // 处理单个方法
  7. if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config])
  8. return this.request({
  9. method: met,
  10. url: arguments[0],
  11. ...arguments[1] || {}
  12. })
  13. } else { // 3个参数(url[,data[,config]])
  14. return this.request({
  15. method: met,
  16. url: arguments[0],
  17. data: arguments[1] || {},
  18. ...arguments[2] || {}
  19. })
  20. }
  21. }
  22. })

我们通过遍历methodsArr数组,依次在Axios.prototype添加对应的方法,注意的是’get’, ‘delete’, ‘head’, 'options'这些方法只接受两个参数。而其他的可接受三个参数,想一下也知道,get不把参数放body的。

但是,你有没有发现,我们只是在Axios的prototype上添加对应的方法,我们导出去的可是request方法啊,那怎么办? 简单,把Axios.prototype上的方法搬运到request上即可。

我们先来实现一个工具方法,实现将b的方法混入a;
 

  1. const utils = {
  2. extend(a,b, context) {
  3. for(let key in b) {
  4. if (b.hasOwnProperty(key)) {
  5. if (typeof b[key] === 'function') {
  6. a[key] = b[key].bind(context);
  7. } else {
  8. a[key] = b[key]
  9. }
  10. }
  11. }
  12. }
  13. }

然后我们就可以利用这个方法将Axios.prototype上的方法搬运到request上啦。

我们修改一下之前的CreateAxiosFn方法即可

  1. function CreateAxiosFn() {
  2. let axios = new Axios();
  3. let req = axios.request.bind(axios);
  4. 增加代码
  5. utils.extend(req, Axios.prototype, axios)
  6. return req;
  7. }

点击查看此时的myAxios.js

现在来测试一下能不能使用axios.get()这种形式调用axios。

  1. <body>
  2. <button class="btn">点我发送请求</button>
  3. <script>
  4. document.querySelector('.btn').onclick = function() {
  5. axios.get('http://localhost:5000/getTest')
  6. .then(res => {
  7. console.log('getAxios 成功响应', res);
  8. })
  9. }
  10. </script>
  11. </body>

害,又是意料之中成功。

再完成下一个功能之前,先给上目前myAxios.js的完整代码

  1. class Axios {
  2. constructor() {
  3. }
  4. request(config) {
  5. return new Promise(resolve => {
  6. const {url = '', method = 'get', data = {}} = config;
  7. // 发送ajax请求
  8. console.log(config);
  9. const xhr = new XMLHttpRequest();
  10. xhr.open(method, url, true);
  11. xhr.onload = function() {
  12. console.log(xhr.responseText)
  13. resolve(xhr.responseText);
  14. }
  15. xhr.send(data);
  16. })
  17. }
  18. }
  19. // 定义get,post...方法,挂在到Axios原型上
  20. const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];
  21. methodsArr.forEach(met => {
  22. Axios.prototype[met] = function() {
  23. console.log('执行'+met+'方法');
  24. // 处理单个方法
  25. if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config])
  26. return this.request({
  27. method: met,
  28. url: arguments[0],
  29. ...arguments[1] || {}
  30. })
  31. } else { // 3个参数(url[,data[,config]])
  32. return this.request({
  33. method: met,
  34. url: arguments[0],
  35. data: arguments[1] || {},
  36. ...arguments[2] || {}
  37. })
  38. }
  39. }
  40. })
  41. // 工具方法,实现b的方法或属性混入a;
  42. // 方法也要混入进去
  43. const utils = {
  44. extend(a,b, context) {
  45. for(let key in b) {
  46. if (b.hasOwnProperty(key)) {
  47. if (typeof b[key] === 'function') {
  48. a[key] = b[key].bind(context);
  49. } else {
  50. a[key] = b[key]
  51. }
  52. }
  53. }
  54. }
  55. }
  56. // 最终导出axios的方法-》即实例的request方法
  57. function CreateAxiosFn() {
  58. let axios = new Axios();
  59. let req = axios.request.bind(axios);
  60. // 混入方法, 处理axios的request方法,使之拥有get,post...方法
  61. utils.extend(req, Axios.prototype, axios)
  62. return req;
  63. }
  64. // 得到最后的全局变量axios
  65. let axios = CreateAxiosFn();

四、请求和响应拦截器

我们先看下拦截器的使用

  1. // 添加请求拦截器
  2. axios.interceptors.request.use(function (config) {
  3. // 在发送请求之前做些什么
  4. return config;
  5. }, function (error) {
  6. // 对请求错误做些什么
  7. return Promise.reject(error);
  8. });
  9. // 添加响应拦截器
  10. axios.interceptors.response.use(function (response) {
  11. // 对响应数据做点什么
  12. return response;
  13. }, function (error) {
  14. // 对响应错误做点什么
  15. return Promise.reject(error);
  16. });

拦截器是什么意思呢?其实就是在我们发送一个请求的时候会先执行请求拦截器的代码,然后再真正地执行我们发送的请求,这个过程会对config,也就是我们发送请求时传送的参数进行一些操作。

而当接收响应的时候,会先执行响应拦截器的代码,然后再把响应的数据返回来,这个过程会对response,也就是响应的数据进行一系列操作。

怎么实现呢?需要明确的是拦截器也是一个类,管理响应和请求。因此我们先实现拦截器
 

  1. class InterceptorsManage {
  2. constructor() {
  3. this.handlers = [];
  4. }
  5. use(fullfield, rejected) {
  6. this.handlers.push({
  7. fullfield,
  8. rejected
  9. })
  10. }
  11. }

我们是用这个语句axios.interceptors.response.useaxios.interceptors.request.use,来触发拦截器执行use方法的。

说明axios上有一个响应拦截器和一个请求拦截器。那怎么实现Axios呢?看代码

  1. class Axios {
  2. constructor() {
  3. 新增代码
  4. this.interceptors = {
  5. request: new InterceptorsManage,
  6. response: new InterceptorsManage
  7. }
  8. }
  9. request(config) {
  10. return new Promise(resolve => {
  11. const {url = '', method = 'get', data = {}} = config;
  12. // 发送ajax请求
  13. console.log(config);
  14. const xhr = new XMLHttpRequest();
  15. xhr.open(method, url, true);
  16. xhr.onload = function() {
  17. console.log(xhr.responseText)
  18. resolve(xhr.responseText);
  19. };
  20. xhr.send(data);
  21. })
  22. }
  23. }

可见,axios实例上有一个对象interceptors。这个对象有两个拦截器,一个用来处理请求,一个用来处理响应。

所以,我们执行语句axios.interceptors.response.use和axios.interceptors.request.use的时候,实现获取axios实例上的interceptors对象,然后再获取response或request拦截器,再执行对应的拦截器的use方法。

而执行use方法,会把我们传入的回调函数push到拦截器的handlers数组里。

到这里你有没有发现一个问题。这个interceptors对象是Axios上的啊,我们导出的是request方法啊(欸?好熟悉的问题,上面提到过哈哈哈~~~额)。处理方法跟上面处理的方式一样,都是把Axios上的方法和属性搬到request过去,也就是遍历Axios实例上的方法,得以将interceptors对象挂载到request上。

所以只要更改下CreateAxiosFn方法即可。

  1. function CreateAxiosFn() {
  2. let axios = new Axios();
  3. let req = axios.request.bind(axios);
  4. // 混入方法, 处理axios的request方法,使之拥有get,post...方法
  5. utils.extend(req, Axios.prototype, axios)
  6. 新增代码
  7. utils.extend(req, axios)
  8. return req;
  9. }

好了,现在request也有了interceptors对象,那么什么时候拿interceptors对象中的handler之前保存的回调函数出来执行。

没错,就是我们发送请求的时候,会先获取request拦截器的handlers的方法来执行。再执行我们发送的请求,然后获取response拦截器的handlers的方法来执行。

因此,我们要修改之前所写的request方法
之前是这样的。
 

  1. request(config) {
  2. return new Promise(resolve => {
  3. const {url = '', method = 'get', data = {}} = config;
  4. // 发送ajax请求
  5. console.log(config);
  6. const xhr = new XMLHttpRequest();
  7. xhr.open(method, url, true);
  8. xhr.onload = function() {
  9. console.log(xhr.responseText)
  10. resolve(xhr.responseText);
  11. };
  12. xhr.send(data);
  13. })
  14. }

但是现在request里不仅要执行发送ajax请求,还要执行拦截器handlers中的回调函数。所以,最好下就是将执行ajax的请求封装成一个方法

  1. request(config) {
  2. this.sendAjax(config)
  3. }
  4. sendAjax(config){
  5. return new Promise(resolve => {
  6. const {url = '', method = 'get', data = {}} = config;
  7. // 发送ajax请求
  8. console.log(config);
  9. const xhr = new XMLHttpRequest();
  10. xhr.open(method, url, true);
  11. xhr.onload = function() {
  12. console.log(xhr.responseText)
  13. resolve(xhr.responseText);
  14. };
  15. xhr.send(data);
  16. })
  17. }

好了,现在我们要获得handlers中的回调

  1. request(config) {
  2. // 拦截器和请求组装队列
  3. let chain = [this.sendAjax.bind(this), undefined] // 成对出现的,失败回调暂时不处理
  4. // 请求拦截
  5. this.interceptors.request.handlers.forEach(interceptor => {
  6. chain.unshift(interceptor.fullfield, interceptor.rejected)
  7. })
  8. // 响应拦截
  9. this.interceptors.response.handlers.forEach(interceptor => {
  10. chain.push(interceptor.fullfield, interceptor.rejected)
  11. })
  12. // 执行队列,每次执行一对,并给promise赋最新的值
  13. let promise = Promise.resolve(config);
  14. while(chain.length > 0) {
  15. promise = promise.then(chain.shift(), chain.shift())
  16. }
  17. return promise;
  18. }

我们先把sendAjax请求和undefined放进了chain数组里,再把请求拦截器的handlers的成对回调放到chain数组头部。再把响应拦截器的handlers的承兑回调反倒chain数组的尾部。

然后再 逐渐取数 chain数组的成对回调执行。

promise = promise.then(chain.shift(), chain.shift())

这一句,实际上就是不断将config从上一个promise传递到下一个promise,期间可能回调config做出一些修改。什么意思?我们结合一个例子来讲解一下

首先拦截器是这样使用的

  1. // 添加请求拦截器
  2. axios.interceptors.request.use(function (config) {
  3. // 在发送请求之前做些什么
  4. return config;
  5. }, function (error) {
  6. // 对请求错误做些什么
  7. return Promise.reject(error);
  8. });
  9. // 添加响应拦截器
  10. axios.interceptors.response.use(function (response) {
  11. // 对响应数据做点什么
  12. return response;
  13. }, function (error) {
  14. // 对响应错误做点什么
  15. return Promise.reject(error);
  16. });

然后执行request的时候。chain数组的数据是这样的

  1. chain = [
  2. function (config) {
  3. // 在发送请求之前做些什么
  4. return config;
  5. },
  6. function (error) {
  7. // 对请求错误做些什么
  8. return Promise.reject(error);
  9. }
  10. this.sendAjax.bind(this),
  11. undefined,
  12. function (response) {
  13. // 对响应数据做点什么
  14. return response;
  15. },
  16. function (error) {
  17. // 对响应错误做点什么
  18. return Promise.reject(error);
  19. }
  20. ]

首先

执行第一次promise.then(chain.shift(), chain.shift()),即

  1. promise.then(
  2. function (config) {
  3. // 在发送请求之前做些什么
  4. return config;
  5. },
  6. function (error) {
  7. // 对请求错误做些什么
  8. return Promise.reject(error);
  9. }
  10. )

一般情况,promise是resolved状态,是执行成功回调的,也就是执行

  1. function (config) {
  2. // 在发送请求之前做些什么
  3. return config;
  4. },

而promise.then是要返回一个新的promise对象的。

为了区分,在这里,我会把这个新的promise对象叫做第一个新的promise对象
这个第一个新的promise对象会把

 

  1. promise.then(
  2. function (response) {
  3. // 对响应数据做点什么
  4. return response;
  5. },
  6. function (error) {
  7. // 对响应错误做点什么
  8. return Promise.reject(error);
  9. }
  10. )

 

  1. class InterceptorsManage {
  2. constructor() {
  3. this.handlers = [];
  4. }
  5. use(fullfield, rejected) {
  6. this.handlers.push({
  7. fullfield,
  8. rejected
  9. })
  10. }
  11. }
  12. class Axios {
  13. constructor() {
  14. this.interceptors = {
  15. request: new InterceptorsManage,
  16. response: new InterceptorsManage
  17. }
  18. }
  19. request(config) {
  20. // 拦截器和请求组装队列
  21. let chain = [this.sendAjax.bind(this), undefined] // 成对出现的,失败回调暂时不处理
  22. // 请求拦截
  23. this.interceptors.request.handlers.forEach(interceptor => {
  24. chain.unshift(interceptor.fullfield, interceptor.rejected)
  25. })
  26. // 响应拦截
  27. this.interceptors.response.handlers.forEach(interceptor => {
  28. chain.push(interceptor.fullfield, interceptor.rejected)
  29. })
  30. // 执行队列,每次执行一对,并给promise赋最新的值
  31. let promise = Promise.resolve(config);
  32. while(chain.length > 0) {
  33. promise = promise.then(chain.shift(), chain.shift())
  34. }
  35. return promise;
  36. }
  37. sendAjax(){
  38. return new Promise(resolve => {
  39. const {url = '', method = 'get', data = {}} = config;
  40. // 发送ajax请求
  41. console.log(config);
  42. const xhr = new XMLHttpRequest();
  43. xhr.open(method, url, true);
  44. xhr.onload = function() {
  45. console.log(xhr.responseText)
  46. resolve(xhr.responseText);
  47. };
  48. xhr.send(data);
  49. })
  50. }
  51. }
  52. // 定义get,post...方法,挂在到Axios原型上
  53. const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];
  54. methodsArr.forEach(met => {
  55. Axios.prototype[met] = function() {
  56. console.log('执行'+met+'方法');
  57. // 处理单个方法
  58. if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config])
  59. return this.request({
  60. method: met,
  61. url: arguments[0],
  62. ...arguments[1] || {}
  63. })
  64. } else { // 3个参数(url[,data[,config]])
  65. return this.request({
  66. method: met,
  67. url: arguments[0],
  68. data: arguments[1] || {},
  69. ...arguments[2] || {}
  70. })
  71. }
  72. }
  73. })
  74. // 工具方法,实现b的方法混入a;
  75. // 方法也要混入进去
  76. const utils = {
  77. extend(a,b, context) {
  78. for(let key in b) {
  79. if (b.hasOwnProperty(key)) {
  80. if (typeof b[key] === 'function') {
  81. a[key] = b[key].bind(context);
  82. } else {
  83. a[key] = b[key]
  84. }
  85. }
  86. }
  87. }
  88. }
  89. // 最终导出axios的方法-》即实例的request方法
  90. function CreateAxiosFn() {
  91. let axios = new Axios();
  92. let req = axios.request.bind(axios);
  93. // 混入方法, 处理axios的request方法,使之拥有get,post...方法
  94. utils.extend(req, Axios.prototype, axios)
  95. return req;
  96. }
  97. // 得到最后的全局变量axios
  98. let axios = CreateAxiosFn();

来测试下拦截器功能是否正常

  1. <script type="text/javascript" src="./myAxios.js"></script>
  2. <body>
  3. <button class="btn">点我发送请求</button>
  4. <script>
  5. // 添加请求拦截器
  6. axios.interceptors.request.use(function (config) {
  7. // 在发送请求之前做些什么
  8. config.method = "get";
  9. console.log("被我请求拦截器拦截了,哈哈:",config);
  10. return config;
  11. }, function (error) {
  12. // 对请求错误做些什么
  13. return Promise.reject(error);
  14. });
  15. // 添加响应拦截器
  16. axios.interceptors.response.use(function (response) {
  17. // 对响应数据做点什么
  18. console.log("被我响应拦截拦截了,哈哈 ");
  19. response = {message:"响应数据被我替换了,啊哈哈哈"}
  20. return response;
  21. }, function (error) {
  22. // 对响应错误做点什么
  23. console.log("错了吗");
  24. return Promise.reject(error);
  25. });
  26. document.querySelector('.btn').onclick = function() {
  27. // 分别使用以下方法调用,查看myaxios的效果
  28. axios({
  29. url: 'http://localhost:5000/getTest'
  30. }).then(res => {
  31. console.log('response', res);
  32. })
  33. }
  34. </script>
  35. </body>

本文链接:手写axios核心原理,再也不怕面试官问我axios原理_前端阳光的博客-CSDN博客_手写axios

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/266000
推荐阅读
相关标签
  

闽ICP备14008679号