最近在看之前项目对axios请求进行的二次封装,突然对axios的源码感了兴趣,想学习一下axios源码。本文用于记录笔者学习了一些主要的功能函数之后的总结和收获。
Axios的目录结构
Axios的主要功能在lib这个文件夹下面,我们就先分析一下这个文件下面每个文件是干什么的吧!
Axios创建
我们知道当我们发送axios请求的时候,可以通过下面两中方式进行发送
// 方式一,通过向axios({})里面传入配置对象 axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred', lastName: 'Flintstone' } }); // 方式二,通过axios.get/post/delete... axios.get('/user?ID=12345')
这说明了axios实例既可以使用函数调用的方式,也可以通过对象身上的属性来调用。我们来看看具体怎么实现。
function createInstance(defaultConfig) { var context = new Axios(defaultConfig); var instance = bind(Axios.prototype.request, context); // Copy axios.prototype to instance utils.extend(instance, Axios.prototype, context); // Copy context to instance utils.extend(instance, context); // Factory for creating new instances instance.create = function create(instanceConfig) { return createInstance(mergeConfig(defaultConfig, instanceConfig)); }; return instance; } var axios = createInstance(defaults);
我们先来看一下axios.js这个文件中的一段代码,里面有一个createInstance函数,传入的参数的是默认的配置。它主要做了以下方面的事情。
- new Axios,创建了axios实例命名为context。下面是Axios中的属性
function Axios(instanceConfig) { // 配置 this.defaults = instanceConfig; //拦截器 this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; } Axios.prototype.request = function request(configOrUrl, config){ xxxxxx } Axios.prototype.getUri = function getUri(config) { xxxxxx } // 给Axios原型上添加get/head/options/delete方法 utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { Axios.prototype[method] = function(url, config) { return this.request(mergeConfig(config || {}, { method: method, url: url, data: (config || {}).data })); }; });
这里的bind方法是axios自己封装的,相当于Axios.prototype.request.bind(context),得到的instance是一个函数。现在instance就相当于wrap这个函数,instance执行结果就相当于Axios.prototype.request执行的执行结果。现在我们可以通过instance('./api/...')来发送请求,但是还不能通过instance.get([url])来发送
module.exports = function bind(fn, thisArg) { return function wrap() { // 这里面的arguments应该是调用instacnce中传入的参数 var args = new Array(arguments.length); for (var i = 0; i < args.length; i++) { args[i] = arguments[i]; } return fn.apply(thisArg, args); }; };
- 然后将axios.prototype上面的方法添加到instance实例上面
- 再将context上面的属性(
defaults
,interceptors
)添加到instance上面 - 然后再合并默认的配置文件和我们传入的配置文件
综上,当我们var axios = createInstance(defaults);
的时候,就会得到一个axios实例,其中的this是指向new Axios()得到的对象,即我们可以调用Axios上的方法。并且,createInstance执行返回的是一个函数,也可以通过函数执行的方式来发起请求。
模拟axios创建过程
// 首先有一个Axios的构造函数,参数是axios的默认配置 function Axios(instanceConfig){ this.defaults = instanceConfig; // interceptors是拦截器,我们先给它一个空数组,后面再来实现 this.interceptors = { request:[], response:[], } } // 原型链添加属性和方法 Axios.prototype.request = function(instanceConfig){ console.log('发送Ajax请求' + instanceConfig.method); } Axios.prototype.get = function(instanceConfig){ return this.request({method:'GET'}) } Axios.prototype.post = function(instanceConfig){ return this.request({method:'POST'}) } // 声明函数,创建Axios实例 function createInstance(instanceConfig){ // 实例化了一个Axios对象,但是现在我们只能调用它身上的方法,还不能当成函数调用context({}) let context = new Axios(instanceConfig); //创建请求函数,bind返回的一个新的函数,并将指向改变,axios源码中的bind方法是他们自己是实现的 // instance是一个函数,可以通过instance({})发请求, 但是还不能使用instance.get()/instace.post() let instance = Axios.prototype.request.bind(context) // 添加get,post,request方法 Object.keys(Axios.prototype).forEach((key) => { instance[key] = Axios.prototype[key].bind(context) }) // 绑定this.default,this.interceptor Object.keys(context).forEach(key => { instance[key] = context[key]; }) return instance; }
axios既可以用在浏览器环境,也可以用在node环境,它是通过适配器模式并进行如下判断的
// node环境 console.log(typeof process) console.log(Object.prototype.toString.call(process)) // 浏览器环境 typeof XMLHttpRequest
收获
- 了解了call,apply,bind的区别
- 了解了axios的创建过程,为什么既可以通过函数调用的方式也可以通过对象上属性的方式发送请求