文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

手写Axios核心原理,再也不怕面试官问我Axios原理

2024-12-03 18:46

关注

 手写axios核心原理

一、axios简介

二、基本使用方式

三、实现axios和axios.method

四、请求和响应拦截器

 

一、axios简介
axios是什么?
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

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

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

  1. axios还是属于 XMLHttpRequest, 因此需要实现一个ajax。或者基于http 。
  2. 还需要一个promise对象来对结果进行处理。

有什么不理解的或者是建议欢迎评论提出.项目已经放到github.可以的话给个star吧!谢谢 github:https://github.com/Sunny-lucking/howToBuildMyAxios

二、基本使用方式
axios基本使用方式主要有

  1. axios(config)
  2. axios.method(url, data , config)
  1. // index.html文件 
  2.  
  3. "text/javascript" src="./myaxios.js"
  4.  
  5. "btn">点我发送请求 
  6.  
  7.  
  8.  
  9.  

三、实现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.     } 
  5.  
  6.     request(config) { 
  7.         return new Promise(resolve => { 
  8.             const {url = '', method = 'get', data = {}} = config; 
  9.             // 发送ajax请求 
  10.             const xhr = new XMLHttpRequest(); 
  11.             xhr.open(method, url, true); 
  12.             xhr.onload = function() { 
  13.                 console.log(xhr.responseText) 
  14.                 resolve(xhr.responseText); 
  15.             } 
  16.             xhr.send(data); 
  17.         }) 
  18.     } 

怎么导出呢?十分简单,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; 
  1. // 得到最后的全局变量axios 
  2. 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. //设置允许跨域访问该服务. 
  6. app.all('*'function (req, res, next) { 
  7.     res.header('Access-Control-Allow-Origin''*'); 
  8.     res.header('Access-Control-Allow-Headers''Content-Type'); 
  9.     res.header('Access-Control-Allow-Methods''*'); 
  10.     res.header('Content-Type''application/json;charset=utf-8'); 
  11.     next(); 
  12. }); 
  13.  
  14. app.get('/getTest'function(request, response){ 
  15.     data = { 
  16.         'FrontEnd':'前端'
  17.         'Sunny':'阳光' 
  18.     }; 
  19.     response.json(data); 
  20. }); 
  21. var server = app.listen(5000, function(){ 
  22.     console.log("服务器启动"); 
  23. }); 
  1. //index.html 
  2. "text/javascript" src="./myAxios.js"
  3.  
  4.  
  5. "btn">点我发送请求 
  6.  
  7.  

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

发现确实成功。

可喜可贺

现在我们来实现下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.     } 
  23. }) 

我们通过遍历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.    
  4.   let req = axios.request.bind(axios); 
  5.   增加代码 
  6.   utils.extend(req, Axios.prototype, axios) 
  7.    
  8.   return req; 

点击查看此时的myAxios.js

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

  1.  
  2. "btn">点我发送请求 
  3.  
  4.  

又是意料之中成功。再完成下一个功能之前,先给上目前myAxios.js的完整代码

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

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

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

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

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

我们是用这个语句axios.interceptors.response.use和axios.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.  
  10.     request(config) { 
  11.         return new Promise(resolve => { 
  12.             const {url = '', method = 'get', data = {}} = config; 
  13.             // 发送ajax请求 
  14.             console.log(config); 
  15.             const xhr = new XMLHttpRequest(); 
  16.             xhr.open(method, url, true); 
  17.             xhr.onload = function() { 
  18.                 console.log(xhr.responseText) 
  19.                 resolve(xhr.responseText); 
  20.             }; 
  21.             xhr.send(data); 
  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.    
  4.   let req = axios.request.bind(axios); 
  5.   // 混入方法, 处理axios的request方法,使之拥有get,post...方法 
  6.   utils.extend(req, Axios.prototype, axios) 
  7.   新增代码 
  8.   utils.extend(req, axios) 
  9.   return req; 

好了,现在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.     }) 

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

  1. request(config) { 
  2.     this.sendAjax(config) 
  3. sendAjax(config){ 
  4.     return new Promise(resolve => { 
  5.         const {url = '', method = 'get', data = {}} = config; 
  6.         // 发送ajax请求 
  7.         console.log(config); 
  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.     }) 

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

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

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

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

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

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

首先拦截器是这样使用的

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

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

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

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

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

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

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

promise.then是要返回一个新的promise对象的。为了区分,在这里,我会把这个新的promise对象叫做第一个新的promise对象 这个第一个新的promise对象会把

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

的执行结果传入resolve函数中

  1. resolve(config) 

使得这个返回的第一个新的promise对象的状态为resovled,而且第一个新的promise对象的data为config。

这里需要对Promise的原理足够理解。所以我前一篇文章写的是手写Promise核心原理,再也不怕面试官问我Promise原理,你可以去看看

接下来,再执行

  1. promise.then
  2.   sendAjax(config) 
  3.   , 
  4.   undefined 

注意:这里的promise是 上面提到的第一个新的promise对象。

而promise.then这个的执行又会返回第二个新的promise对象。

因为这里promise.then中的promise也就是第一个新的promise对象的状态是resolved的,所以会执行sendAjax()。而且会取出第一个新的promise对象的data 作为config转入sendAjax()。

当sendAjax执行完,就会返回一个response。这个response就会保存在第二个新的promise对象的data中。

接下来,再执行

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

同样,会把第二个新的promise对象的data取出来作为response参数传入

  1. function (response) { 
  2.     // 对响应数据做点什么 
  3.     return response; 
  4.   },  

饭后返回一个promise对象,这个promise对象的data保存了这个函数的执行结果,也就是返回值response。

然后通过return promise;

把这个promise返回了。咦?是怎么取出promise的data的。我们看看我们平常事怎么获得响应数据的

  1. axios.get('http://localhost:5000/getTest'
  2.     .then(res => { 
  3.          console.log('getAxios 成功响应', res); 
  4.     }) 

在then里接收响应数据。所以原理跟上面一样,将返回的promise的data作为res参数了。

现在看看我们的myAxios完整代码吧,好有个全面的了解

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

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

  1. "text/javascript" src="./myAxios.js"
  2.  
  3.  
  4. "btn">点我发送请求 
  5.  
  6.  

拦截成功!!!!!

 

来源:前端阳光内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯