axios
是一个基于 promise 的 HTTP 库,能够用在浏览器和 node.js 中。这里将会从功能出发,分析源码,深刻了解 axios 是怎么实现这些功能的。node
IDE: WebStorm
Git地址: https://github.com/cookhot/ax... 注意analysis
分支
中文文档: https://www.kancloud.cn/yunye...ios
项目的入口是axios.js
, 当axios
在被引入项目中的时候,导入的实际上是一个方法,能够直接调用此方法发起请求。git
import axios from './axios' console.log(typeof axios); // function axios({ url: 'http://localhost:8088/index' }).then((res) => { console.log(res) })
axios.jsgithub
'use strict'; var utils = require('./utils'); var bind = require('./helpers/bind'); var Axios = require('./core/Axios'); var defaults = require('./defaults'); /** * 建立 Axios 的一个实例 * Create an instance of Axios * * @param {Object} defaultConfig The default config for the instance * @return {Axios} A new instance of Axios */ function createInstance(defaultConfig) { var context = new Axios(defaultConfig); // instance 是一个方法, 实际上就是 Axios.prorotype.request, 方法的 this => context var instance = bind(Axios.prototype.request, context); // 把 Axios 原型上面的属性(方法)复制到 instance 上面,保证被复制的方法中 this => context // 注意 utils.extend 和 utils.merge的区别,二者是不一样的 utils.extend(instance, Axios.prototype, context); // Copy context to instance // context 上面的属性都复制到 instance,context.defaults 和 context.interceptors 经过instance可以访问 utils.extend(instance, context); return instance; } // Create the default instance to be exported var axios = createInstance(defaults); // Expose Axios class to allow class inheritance axios.Axios = Axios; // Factory for creating new instances // 建立 Axios 实例 axios.create = function create(instanceConfig) { // instanceConfig 是开发者提供的配置属性,将会和 Axios 提供的默认配置属性合并, // 造成的新的配置属性将会是实例请求的默认属性 (很经常使用的设计方法) return createInstance(utils.merge(defaults, instanceConfig)); }; // Expose Cancel & CancelToken // 请求取消 axios.Cancel = require('./cancel/Cancel'); axios.CancelToken = require('./cancel/CancelToken'); axios.isCancel = require('./cancel/isCancel'); // Expose all/spread axios.all = function all(promises) { return Promise.all(promises); }; axios.spread = require('./helpers/spread'); // 输出Axios module.exports = axios; // Allow use of default import syntax in TypeScript module.exports.default = axios;
从上面的源码中,可以看到axios
其实就是调用的Axios.prototype.request
方法,为了防止在运行时候this指向异常,显示的绑定上了context。ajax
// ...... bind 的实现 module.exports = function bind(fn, thisArg) { // 使用闭包 和 apply 改变 fn 的 this 指向 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); }; };
为了可以让开发人员更好的调用get、post、...... 等等方法, 因而把Axios.prototype
上面的方法都复制到axios上面。 相应的为了防止这些方法中this指向异常,也显示的绑定context, 具体的实现逻辑请看下面 ⤵️ 对象的复制。 后面的 utils.extend(instance, context)
这行代码是为了帮助咱们可以经过axios 访问到 context 上面的属性, context里面包含拦截器(interceptors)以及配置属性值(defaults)。axios
axios提供了两种的方式来处理对象的合并, 分别是 merge 与 extend。代码被放在utils.js数组
// utils.js // ....... /** * Iterate over an Array or an Object invoking a function for each item. * * If `obj` is an Array callback will be called passing * the value, index, and complete array for each item. * * If 'obj' is an Object callback will be called passing * the value, key, and complete object for each property. * * @param {Object|Array} obj The object to iterate * @param {Function} fn The callback to invoke for each item */ function forEach(obj, fn) { // Don't bother if no value provided if (obj === null || typeof obj === 'undefined') { return; } // Force an array if not already something iterable if (typeof obj !== 'object') { /*eslint no-param-reassign:0*/ obj = [obj]; } if (isArray(obj)) { // Iterate over array values for (var i = 0, l = obj.length; i < l; i++) { fn.call(null, obj[i], i, obj); } } else { // Iterate over object keys for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { fn.call(null, obj[key], key, obj); } } } } /** * Accepts varargs expecting each argument to be an object, then * immutably merges the properties of each object and returns result. * * When multiple objects contain the same key the later object in * the arguments list will take precedence. * * Example: * * ```js * var result = merge({foo: 123}, {foo: 456}); * console.log(result.foo); // outputs 456 * ``` * * @param {Object} obj1 Object to merge * @returns {Object} Result of all merge properties */ function merge(/* obj1, obj2, obj3, ... */) { // 使用新的对象,这样就能防止传入的对象在合并的时候被改变 var result = {}; function assignValue(val, key) { // 对象的属性复制的时候,当两个属性都是都是对象的时候,就对此属性对象中子属性再进行在复制。 // 做用应该是为了防止前属性对象的属性全被覆盖掉 if (typeof result[key] === 'object' && typeof val === 'object') { result[key] = merge(result[key], val); } else { result[key] = val; } } for (var i = 0, l = arguments.length; i < l; i++) { forEach(arguments[i], assignValue); } return result; } /** * Extends object a by mutably adding to it the properties of object b. * * 这里考虑了复制函数时候的 this 指向问题,设计的很好,之后能够借鉴 * @param {Object} a The object to be extended * @param {Object} b The object to copy properties from * @param {Object} thisArg The object to bind function to * @return {Object} The resulting value of object a */ function extend(a, b, thisArg) { forEach(b, function assignValue(val, key) { if (thisArg && typeof val === 'function') { a[key] = bind(val, thisArg); } else { a[key] = val; } }); return a; }
merge
相似于咱们常常使用的对象浅拷贝,可是又不全是浅拷贝。在拷贝的时候,发现进行拷贝的两个属性都是都是对象的时候,就对此属性对象中子属性再进行在复制。用于防止前面一个属性对象中的子属性值被全覆盖掉。extend
也是对象的浅拷贝,不过在拷贝方法的时候,会显示指定方法的this,用于防止this指向异常。promise
axios建立请求后会返回一个Promise的实例,Promise.all
所返回的promise实例会在传入的promise实例状态都发生变化,才变动状态。因此 axios.all
其实就是调用Promise.all
。浏览器
axios.all = function all(promises) { return Promise.all(promises); };
cancel 这里暂时不讨论,后面经过结合 XMLHttpRequest 与 node 的 http 会说的明白的更加清楚。闭包
在看完axios.js
之后,就须要开始了解Axios
构造函数的实现了。
Axios.js
'use strict'; var defaults = require('./../defaults'); var utils = require('./../utils'); var InterceptorManager = require('./InterceptorManager'); var dispatchRequest = require('./dispatchRequest'); /** * Create a new instance of Axios * * @param {Object} instanceConfig The default config for the instance */ function Axios(instanceConfig) { // instanceConfig => 建立对象的设置的默认值 // Axios 中 defaults 分为三个层次, Axios 默认的defaults < 建立实例传入的defaults < 调用方法时候传入的defaults // 我的感受使用 this.defaults = utils.merge(defaults, instanceConfig) 会更好,当后面使用request发起请求的时候,代码变化以下: /* config = utils.merge(defaults, this.defaults, config); 老代码 config = utils.merge(this.defaults, config); // 新代码 */ this.defaults = instanceConfig; // 拦截器 this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; } /** * Dispatch a request * * @param {Object} config The config specific for this request (merged with this.defaults) */ Axios.prototype.request = function request(config) { /*eslint no-param-reassign:0*/ // Allow for axios('example/url'[, config]) a la fetch API // 重载 request(url, config) // 能够支持request (config) 也能够支持 request(url, config) if (typeof config === 'string') { config = utils.merge({ url: arguments[0] }, arguments[1]); } config = utils.merge(defaults, this.defaults, config); config.method = config.method.toLowerCase(); // Hook up interceptors middleware // 拦截器设计处理 // chain 是一个数组 var chain = [dispatchRequest, undefined]; // promise 是调用头,状态已经改变为 resolved var promise = Promise.resolve(config); // 使用 use 添加 fulfilled 与 rejected 添加到队列中 // 添加 request 拦截函数的时候使用的是unshift, 这样会致使 use 后添加的先执行,先添加的后执行 /* axios.interceptors.request.use(function resolve(config) { console.log("1"); }); axios.interceptors.request.use(function resolve(config) { console.log("2") }) // 结果 2 1 */ // 考虑到后面 是使用 promise的链式调用, 因此在 拦截器的回调方法中 必需要返回一个 config 对象 // 若是不返回 config, 会致使后续请求执行异常 this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); // response 使用的push 添加 拦截函数,这里是添加先执行,后添加后执行 this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); }); // promise 的初始化状态就是 resolved,这里造成了promise调用链,执行流程过程以下 // chain [fulfilled, rejected, ... dispatchRequest, undefined ....,fulfilled, rejected] // 这里补充一下 fulfilled, rejected 都是确定是成对出现的, 具体缘由可看 InterceptorManager.prototype.use // promise.then(undefined, undefined) 中当传递的不是function时,会发生值穿。也就是说 use 中能够传入非function, // 或者传入单个function while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; }; // Provide aliases for supported request methods // 复用request 实现了 delete, get, head, options utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { /*eslint func-names:0*/ Axios.prototype[method] = function(url, config) { return this.request(utils.merge(config || {}, { method: method, url: url })); }; }); // 复用request 实现了 post, put, patch utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { /*eslint func-names:0*/ Axios.prototype[method] = function(url, data, config) { return this.request(utils.merge(config || {}, { method: method, url: url, data: data })); }; }); module.exports = Axios;
Axios.js
主要处理了两个部分,复用请求方法、实现拦截器。
当咱们使用 Axios 的实例去发送请求,使用的方法get、post等都是复用了request方法,在request方法中经过 arguments 获取传入的参数,实现了传入参数的重载。
拦截器是axios的一大特点,它的实现原理其实不复杂,核心就是promise的链式调用。
原理能够参考下图:
而后附上InterceptorManager.js
的源码,可是我的以为这里没有什么好说的,其实就是对一个数组的操做。
'use strict'; var utils = require('./../utils'); function InterceptorManager() { this.handlers = []; } /** * Add a new interceptor to the stack * * @param {Function} fulfilled The function to handle `then` for a `Promise` * @param {Function} rejected The function to handle `reject` for a `Promise` * * @return {Number} An ID used to remove interceptor later */ InterceptorManager.prototype.use = function use(fulfilled, rejected) { // fulfilled => 成功方法 // rejected => 失败方法 this.handlers.push({ fulfilled: fulfilled, rejected: rejected }); return this.handlers.length - 1; }; /** * Remove an interceptor from the stack * * @param {Number} id The ID that was returned by `use` */ // 把数组中 对象设置为 null InterceptorManager.prototype.reject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null; } }; /** * Iterate over all the registered interceptors * * This method is particularly useful for skipping over any * interceptors that may have become `null` calling `eject`. * * @param {Function} fn The function to call for each interceptor */ InterceptorManager.prototype.forEach = function forEach(fn) { // 遍历运行数组 utils.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h); } }); }; module.exports = InterceptorManager;
若是你看完上面的源码解读,可以清楚的明白下面这段代码执行顺序,那么就说明你掌握axios拦截器的实现了
// 拦截器的执行顺序 /* axios.interceptors.request.use(function resolve(config) { console.log("request"); return config; }); axios.interceptors.response.use(function resolve(res) { console.log('response') return res }); axios.get('http://localhost:3000/index').then(function resolve(res) { console.log('ajax'); return res }).then(function(){ console.log('end') }) */
第一篇总算是写完了,后续还有关于axios
3篇的源码解读,若是你们以为写的还行的话,麻烦给一个赞?,鼓励鼓励,谢谢了