写在前面:
距离2021年 还有两个月~11月份的开始,决定勤奋一波
axios 不用更多的介绍,vue官方的推荐是使用axios,vue-resource淡出框架的依赖,在我们进行项目开发和搭建的时候,axios是我们连接和后端接口的桥梁,当然选择axios,自然是其的一些特点,能够更好的满足我们日常的工作;
能学习到什么呢
通过本篇文章,你将大概了解到axios创建实例过程、配置合并流程以及原理,还有进行请求的流程;深入了解到axios的响应拦截器原理,支持多平台(浏览器和node)环境下使用原理、请求响应参数的设置原理、整个的执行过程~嘻嘻,有些地方大家简单大概了解,还是需要看下源码的执行原理滴;
进入axios
想要了解axios的原理,可以从其特点出发;
进入原理探究过程(搓手手)
首先自行下载axios的包,查看其目录结构
在这个里面有两个助手包,一个helpers、一个是utils,这两个包都是axios的工具包,helpers是服务于axios,utils更加广泛的工具,可以在其他的插件中进行引入使用,我们每个插件其实都会自定义自己的工具包~
配置阶段
axios的配置共分为3种,全局配置、实例配置、请求配置;这三个配置和我们进行的操作息息相关;
全局配置
全局配置,更加明确的说其实是axios里面给我们提供的一个默认的选项,当我们导入axios的包的时候,其实就已经进行了默认选项的配置;
默认配置项如下:
而这些全局配置选项也正是我们在进行实例化的时候,可以进行自定义配置的,主要的属性包括;
-
transformRequest :Array 允许在向服务器前发送信息的时候,统一处理请求信息默认配置选项功能:
- 序列化请求参数
- 为不同类型的请求参数添加请求头
-
transformResponse :Array 则是对响应数据的操作处理
- timeout 、headers等默认的配置
- 默认配置如图所示
-
实例配置
实例配置其实是我们针对自己项目进行的个性化的配置,在导入axios时候,就给我们提供了一个create的函数,而这个函数的作用其实是创建一个新的实例,并返回给我们;应用于整个项目配置
在一个项目中每个接口都有共同的配置,或者几个接口有共同的表现形式,每个实例化的axios都有自己的配置,通过全局配置进行初始化,或者合并成一个新的配置项目实例化配置一般是我们自己配置的,我们在项目中可能包含多个模块,项目的接口名字不一致,因此需要配置不同的axios的实例信息,这样我们配置moduleA和moduleB的实例配置,每次返回的新的实例不会互相影响;
create的方法
axios.create = function create(instanceConfig) { // console.log("实例配置",mergeConfig(axios.defaults, instanceConfig)) return createInstance(mergeConfig(axios.defaults, instanceConfig)); };
//定义创建的axios function createInstance(defaultConfig) { var context = new Axios(defaultConfig); // instance 绑定axios的默认数据 instance 返回wrap函数 var instance = bind(Axios.prototype.request, context); // console.log(instance.prototype) // 复制Axios的原型到扩展到实例中 utils.extend(instance, Axios.prototype, context); //将Axios的属性扩展到实例上 utils.extend(instance, context); //instance 进行复制 return instance; }
这块儿其实有点难以理解 ,就是我们创建实例时候,直接new Axios就可以了,为什么还需要进行包装呢??
先来看bind方法 ,bind的方法其实很好理解,就是将Axios原型方法上的request方法绑定在context的上下文中,返回一个wrap的方法
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); }; };
》》 utils.extend(instance, Axios.prototype, context); 这个的意思又是什么呢
function extend(a, b, thisArg) { // console.log("参数b",b) forEach(b, function assignValue(val, key) { if (thisArg && typeof val === 'function') { a[key] = bind(val, thisArg); } else { a[key] = val; } }); return a; }
迷糊人员?这里其实是将Axios上原型上的方法复制到我们的instance的实例上,这样的操作?保持迷惑?
最后一个的extend utils.extend(instance, context); 是将刚刚创建的Axios的实例,复制到instance上,整个创建实例包含的步骤有:
- 将b的属性内容复制给a
- 此时将Axios原型上的方法复制到instance中
- 最后把axios实例复制给instance 形成一个真正的instance
查阅资料,说这样的操作是为了更好的使用axios,如果我们只是返回一个new Axios的实例,那么我们在进行调用的方式是比较单一的,这样子配置了后;调用的方式就多了起来
axios({config}).then()
axios.get('url').then()
这种的实现方式,利用了拷贝继承,打印instance的构造函数;
请求配置
同一个实例会有一些公用的配置项目,如baseUrl,但是很多时候,不同的请求具体的配置是不一样的,如url、method等,所以在请求的时候需要传入的配置与实例配置进行合并;
请求的时候,也进行了相关的配置项目,这样形成了三个配置项,主要采用后配置后优先的原则,优先级顺序:请求配置>实例配置>全局配置;
这个时候便涉及到了合并的问题;主要涉及的配置合并
配置合并主要在/lib/core/mergeConfig.js中进行
module.exports = function mergeConfig(config1, config2) { // eslint-disable-next-line no-param-reassign config2 = config2 || {}; var config = {}; var valueFromConfig2Keys = ['url', 'method', 'params', 'data']; //需要进行深拷贝的属性 var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy']; //默认的配置项目key var defaultToConfig2Keys = [ 'baseURL', 'url', 'transformRequest', 'transformResponse', 'paramsSerializer', 'timeout', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName', 'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 'maxContentLength', 'validateStatus', 'maxRedirects', 'httpAgent', 'httpsAgent', 'cancelToken', 'socketPath' ]; //将传入的配置项目内容先赋值到config中 utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) { if (typeof config2[prop] !== 'undefined') { config[prop] = config2[prop]; } }); // 遍历需要进行深拷贝的属性 /** * config2中的如果是对象 则进行深拷贝 * 如果不是则直接进行赋值,如果该属性对应的值, * 则直接进行拷贝config1的内容 */ utils.forEach(mergeDeepPropertiesKeys, function mergeDeepProperties(prop) { if (utils.isObject(config2[prop])) { config[prop] = utils.deepMerge(config1[prop], config2[prop]); } else if (typeof config2[prop] !== 'undefined') { config[prop] = config2[prop]; } else if (utils.isObject(config1[prop])) { config[prop] = utils.deepMerge(config1[prop]); } else if (typeof config1[prop] !== 'undefined') { config[prop] = config1[prop]; } }); //defaultToConfig2Keys 一些配置 config2的内容存在则使用config2的,不存在使用config1的内容 utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) { if (typeof config2[prop] !== 'undefined') { config[prop] = config2[prop]; } else if (typeof config1[prop] !== 'undefined') { config[prop] = config1[prop]; } }); //请求的一些key var axiosKeys = valueFromConfig2Keys .concat(mergeDeepPropertiesKeys) .concat(defaultToConfig2Keys); //其他的配置key 传入的key的值 var otherKeys = Object .keys(config2) .filter(function filterAxiosKeys(key) { return axiosKeys.indexOf(key) === -1; }); // 将config2中的自定义key的值 存入config中 utils.forEach(otherKeys, function otherKeysDefaultToConfig2(prop) { if (typeof config2[prop] !== 'undefined') { config[prop] = config2[prop]; } else if (typeof config1[prop] !== 'undefined') { config[prop] = config1[prop]; } }); //返回config return config; };