# axios-analysis **Repository Path**: wangchito/axios-analysis ## Basic Information - **Project Name**: axios-analysis - **Description**: 源码调试解析 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-05-27 - **Last Updated**: 2021-05-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: Axios ## README # 学习 axios 源码整体架构,打造属于自己的请求库 ## 1. 前言 >你好,我是[若川](https://lxchuan12.gitee.io),微信搜索[「若川视野」](https://mp.weixin.qq.com/s/c3hFML3XN9KCUetDOZd-DQ)关注我,专注前端技术分享。欢迎加我微信`ruochuan12`,加群交流学习。 >这是`学习源码整体架构系列`第六篇。整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是主线的具体函数的实现。本篇文章学习的是实际仓库的代码。 >[本文仓库地址](https://github.com/lxchuan12/axios-analysis.git):`git clone https://github.com/lxchuan12/axios-analysis.git` >**要是有人说到怎么读源码,正在读文章的你能推荐我的源码系列文章,那真是太好了**。 `学习源码整体架构系列`文章如下: >1.[学习 jQuery 源码整体架构,打造属于自己的 js 类库](https://juejin.im/post/5d39d2cbf265da1bc23fbd42)
>2.[学习 underscore 源码整体架构,打造属于自己的函数式编程类库](https://juejin.im/post/5d4bf94de51d453bb13b65dc)
>3.[学习 lodash 源码整体架构,打造属于自己的函数式编程类库](https://juejin.im/post/5d767e1d6fb9a06b032025ea)
>4.[学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK](https://juejin.im/post/5dba5a39e51d452a2378348a)
>5.[学习 vuex 源码整体架构,打造属于自己的状态管理库](https://juejin.im/post/5dd4e61a6fb9a05a5c010af0)
>6.[学习 axios 源码整体架构,打造属于自己的请求库](https://juejin.im/post/5df349b5518825123751ba66)
>7.[学习 koa 源码的整体架构,浅析koa洋葱模型原理和co原理](https://juejin.im/post/5e69925cf265da571e262fe6)
>8.[学习 redux 源码整体架构,深入理解 redux 及其中间件原理](https://juejin.im/post/5ee63b7d51882542fc6265ad) 感兴趣的读者可以点击阅读。
其他源码计划中的有:[`express`](https://github.com/lxchuan12/express-analysis)、[`vue-rotuer`](https://github.com/lxchuan12/vue-router-analysis)、[`react-redux`](https://github.com/lxchuan12/react-redux-analysis) 等源码,不知何时能写完(哭泣),欢迎持续关注我(若川)。 源码类文章,一般阅读量不高。已经有能力看懂的,自己就看了。不想看,不敢看的就不会去看源码。
所以我的文章,尽量写得让想看源码又不知道怎么看的读者能看懂。 本文比较长,手机上阅读,可以划到有图的地方直接看文中的几张图即可。建议点赞或收藏后在电脑上阅读,按照文中调试方式自己调试或许更容易吸收消化。 **导读**
文章详细介绍了 `axios` 调试方法。详细介绍了 `axios` 构造函数,拦截器,取消等功能的实现。最后还对比了其他请求库。 本文学习的版本是`v0.19.0`。克隆的官方仓库的`master`分支。 截至目前(2019年12月14日),最新一次`commit`是`2019-12-09 15:52 ZhaoXC` `dc4bc49673943e352`,`fix: fix ignore set withCredentials false (#2582)`。 本文仓库在这里[若川的 axios-analysis github 仓库](https://github.com/lxchuan12/axios-analysis)。求个`star`呀。 如果你是求职者,项目写了运用了`axios`,面试官可能会问你: >1.为什么 `axios` 既可以当函数调用,也可以当对象使用,比如`axios({})`、`axios.get`。
>2.简述 `axios` 调用流程。
>3.有用过拦截器吗?原理是怎样的?
>4.有使用`axios`的取消功能吗?是怎么实现的?
>5.为什么支持浏览器中发送请求也支持`node`发送请求?
诸如这类问题。 ## 2. chrome 和 vscode 调试 axios 源码方法 前不久,笔者在知乎回答了一个问题[一年内的前端看不懂前端框架源码怎么办?](https://www.zhihu.com/question/350289336/answer/910970733) 推荐了一些资料,阅读量还不错,大家有兴趣可以看看。主要有四点:
>1.借助调试
>2.搜索查阅相关高赞文章
>3.把不懂的地方记录下来,查阅相关文档
>4.总结
看源码,调试很重要,所以笔者详细写下 `axios` 源码调试方法,帮助一些可能不知道如何调试的读者。 ### 2.1 chrome 调试浏览器环境的 axios 调试方法 `axios`打包后有`sourcemap`文件。 ```bash # 可以克隆笔者的这个仓库代码 git clone https://github.com/lxchuan12/axios-analysis.git cd axios-analaysis/axios npm install npm start # open [http://localhost:3000](http://localhost:3000) # chrome F12 source 控制面板 webpack// . lib 目录下,根据情况自行断点调试 ``` 本文就是通过上述的例子`axios/sandbox/client.html`来调试的。 顺便简单提下调试`example`的例子,虽然文章最开始时写了这部分,后来又删了,最后想想还是写下。 找到文件`axios/examples/server.js`,修改代码如下: ```js server = http.createServer(function (req, res) { var url = req.url; // 调试 examples console.log(url); // Process axios itself if (/axios\.min\.js$/.test(url)) { // 原来的代码 是 axios.min.js // pipeFileToResponse(res, '../dist/axios.min.js', 'text/javascript'); pipeFileToResponse(res, '../dist/axios.js', 'text/javascript'); return; } // 原来的代码 是 axios.min.map // if (/axios\.min.map$/.test(url)) { if (/axios\.map$/.test(url)) { // 原来的代码 是 axios.min.map // pipeFileToResponse(res, '../dist/axios.min.map', 'text/javascript'); pipeFileToResponse(res, '../dist/axios.map', 'text/javascript'); return; } } ``` ```bash # 上述安装好依赖后 # npm run examples 不能同时开启,默认都是3000端口 # 可以指定端口 5000 # npm run examples === node ./examples/server.js node ./examples/server.js -p 5000 ``` 打开[http://localhost:5000](http://localhost:5000),然后就可以开心的在`Chrome`浏览器中调试`examples`里的例子了。 `axios` 是支持 `node` 环境发送请求的。接下来看如何用 `vscode` 调试 `node` 环境下的`axios`。 ### 2.2 vscode 调试 node 环境的 axios 在根目录下 `axios-analysis/` 创建`.vscode/launch.json`文件如下: ```json { // 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Launch Program", "program": "${workspaceFolder}/axios/sandbox/client.js", "skipFiles": [ "/**" ] }, ] } ``` 按`F5`开始调试即可,按照自己的情况,单步跳过`(F10)`、单步调试`(F11)`断点调试。 其实开源项目一般都有贡献指南`axios/CONTRIBUTING.md`,笔者只是把这个指南的基础上修改为引用`sourcemap`的文件可调试。 ## 3. 先看 axios 结构是怎样的 ```bash git clone https://github.com/lxchuan12/axios-analysis.git cd axios-analaysis/axios npm install npm start ``` 按照上文说的调试方法, `npm start` 后,直接在 `chrome` 浏览器中调试。 打开 [http://localhost:3000](http://localhost:3000),在控制台打印出`axios`,估计很多人都没打印出来看过。 ```js console.log({axios: axios}); ``` 层层点开来看,`axios` 的结构是怎样的,先有一个大概印象。 笔者画了一张比较详细的图表示。 ![axios 结构关系图](./images/axios-instance.png) 看完结构图,如果看过`jQuery`、`underscore`和`lodash`源码,会发现其实跟`axios`源码设计类似。 `jQuery` 别名 `$`,`underscore` `loadsh` 别名 `_` 也既是函数,也是对象。比如`jQuery`使用方式。`$('#id')`, `$.ajax`。 接下来看具体源码的实现。可以跟着断点调试一下。 **断点调试要领:**
**赋值语句可以一步跳过,看返回值即可,后续详细再看。**
**函数执行需要断点跟着看,也可以结合注释和上下文倒推这个函数做了什么。**
## 4. axios 源码 初始化 看源码第一步,先看`package.json`。一般都会申明 `main` 主入口文件。 ```json // package.json { "name": "axios", "version": "0.19.0", "description": "Promise based HTTP client for the browser and node.js", "main": "index.js", // ... } ``` 主入口文件 ```js // index.js module.exports = require('./lib/axios'); ``` ### 4.1 `lib/axios.js`主文件 `axios.js`文件 代码相对比较多。分为三部分展开叙述。 >1. 第一部分:引入一些工具函数`utils`、`Axios`构造函数、默认配置`defaults`等。
>2. 第二部分:是生成实例对象 `axios`、`axios.Axios`、`axios.create`等。
>3. 第三部分取消相关API实现,还有`all`、`spread`、导出等实现。
#### 4.1.1 第一部分 引入一些工具函数`utils`、`Axios`构造函数、默认配置`defaults`等。 ```js // 第一部分: // lib/axios // 严格模式 'use strict'; // 引入 utils 对象,有很多工具方法。 var utils = require('./utils'); // 引入 bind 方法 var bind = require('./helpers/bind'); // 核心构造函数 Axios var Axios = require('./core/Axios'); // 合并配置方法 var mergeConfig = require('./core/mergeConfig'); // 引入默认配置 var defaults = require('./defaults'); ``` #### 4.1.2 第二部分 是生成实例对象 `axios`、`axios.Axios`、`axios.create`等。 ```js /** * Create an instance of Axios * * @param {Object} defaultConfig The default config for the instance * @return {Axios} A new instance of Axios */ function createInstance(defaultConfig) { // new 一个 Axios 生成实例对象 var context = new Axios(defaultConfig); // bind 返回一个新的 wrap 函数, // 也就是为什么调用 axios 是调用 Axios.prototype.request 函数的原因 var instance = bind(Axios.prototype.request, context); // Copy axios.prototype to instance // 复制 Axios.prototype 到实例上。 // 也就是为什么 有 axios.get 等别名方法, // 且调用的是 Axios.prototype.get 等别名方法。 utils.extend(instance, Axios.prototype, context); // Copy context to instance // 复制 context 到 intance 实例 // 也就是为什么默认配置 axios.defaults 和拦截器 axios.interceptors 可以使用的原因 // 其实是new Axios().defaults 和 new Axios().interceptors 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 class 允许 class 继承 也就是可以 new axios.Axios() // 但 axios 文档中 并没有提到这个,我们平时也用得少。 axios.Axios = Axios; // Factory for creating new instances // 工厂模式 创建新的实例 用户可以自定义一些参数 axios.create = function create(instanceConfig) { return createInstance(mergeConfig(axios.defaults, instanceConfig)); }; ``` 这里简述下工厂模式。`axios.create`,也就是用户不需要知道内部是怎么实现的。
举个生活的例子,我们买手机,不需要知道手机是怎么做的,就是工厂模式。
看完第二部分,里面涉及几个工具函数,如`bind`、`extend`。接下来讲述这几个工具方法。
#### 4.1.3 工具方法之 bind `axios/lib/helpers/bind.js` ```js 'use strict'; // 返回一个新的函数 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]; } // 把 argument 对象放在数组 args 里 return fn.apply(thisArg, args); }; }; ``` 传递两个参数函数和`thisArg`指向。
把参数`arguments`生成数组,最后调用返回参数结构。
其实现在 `apply` 支持 `arguments`这样的类数组对象了,不需要手动转数组。
那么为啥作者要转数组,为了性能?当时不支持?抑或是作者不知道?这就不得而知了。有读者知道欢迎评论区告诉笔者呀。
关于`apply`、`call`和`bind`等不是很熟悉的读者,可以看笔者的另一个`面试官问系列`。
[面试官问:能否模拟实现JS的bind方法](https://juejin.im/post/5bec4183f265da616b1044d7)
举个例子 ```js function fn(){ console.log.apply(console, arguments); } fn(1,2,3,4,5,6, '若川'); // 1 2 3 4 5 6 '若川' ``` #### 4.1.4 工具方法之 utils.extend `axios/lib/utils.js` ```js 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; } ``` 其实就是遍历参数 `b` 对象,复制到 `a` 对象上,如果是函数就是则用 `bind` 调用。 #### 4.1.5 工具方法之 utils.forEach `axios/lib/utils.js` 遍历数组和对象。设计模式称之为迭代器模式。很多源码都有类似这样的遍历函数。比如大家熟知的`jQuery` `$.each`。 ```js /** * @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 // 判断 null 和 undefined 直接返回 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]; } // 是数组 则用for 循环,调用 fn 函数。参数类似 Array.prototype.forEach 的前三个参数。 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 in 遍历对象,但 for in 会遍历原型链上可遍历的属性。 // 所以用 hasOwnProperty 来过滤自身属性了。 // 其实也可以用Object.keys来遍历,它不遍历原型链上可遍历的属性。 for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { fn.call(null, obj[key], key, obj); } } } } ``` 如果对`Object`相关的`API`不熟悉,可以查看笔者之前写过的一篇文章。[JavaScript 对象所有API解析](https://lxchuan12.gitee.io/js-object-api/) #### 4.1.6 第三部分 取消相关API实现,还有`all`、`spread`、导出等实现。 ```js // Expose Cancel & CancelToken // 导出 Cancel 和 CancelToken axios.Cancel = require('./cancel/Cancel'); axios.CancelToken = require('./cancel/CancelToken'); axios.isCancel = require('./cancel/isCancel'); // Expose all/spread // 导出 all 和 spread API axios.all = function all(promises) { return Promise.all(promises); }; axios.spread = require('./helpers/spread'); module.exports = axios; // Allow use of default import syntax in TypeScript // 也就是可以以下方式引入 // import axios from 'axios'; module.exports.default = axios; ``` 这里介绍下 `spread`,取消的`API`暂时不做分析,后文再详细分析。 假设你有这样的需求。 ```js function f(x, y, z) {} var args = [1, 2, 3]; f.apply(null, args); ``` 那么可以用`spread`方法。用法: ```js axios.spread(function(x, y, z) {})([1, 2, 3]); ``` 实现也比较简单。源码实现: ```js /** * @param {Function} callback * @returns {Function} */ module.exports = function spread(callback) { return function wrap(arr) { return callback.apply(null, arr); }; }; ``` 上文`var context = new Axios(defaultConfig);`,接下来介绍核心构造函数`Axios`。 ### 4.2 核心构造函数 Axios `axios/lib/core/Axios.js` 构造函数`Axios`。 ```js function Axios(instanceConfig) { // 默认参数 this.defaults = instanceConfig; // 拦截器 请求和响应拦截器 this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; } ``` ```js Axios.prototype.request = function(config){ // 省略,这个是核心方法,后文结合例子详细描述 // code ... var promise = Promise.resolve(config); // code ... return promise; } // 这是获取 Uri 的函数,这里省略 Axios.prototype.getUri = function(){} // 提供一些请求方法的别名 // Provide aliases for supported request methods // 遍历执行 // 也就是为啥我们可以 axios.get 等别名的方式调用,而且调用的是 Axios.prototype.request 方法 // 这个也在上面的 axios 结构图上有所体现。 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 })); }; }); 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; ``` 接下来看拦截器部分。 ### 4.3 拦截器管理构造函数 InterceptorManager 请求前拦截,和请求后拦截。
在`Axios.prototype.request`函数里使用,具体怎么实现的拦截的,后文配合例子详细讲述。
[axios github 仓库 拦截器文档](https://github.com/axios/axios#interceptors) 如何使用: ```js // Add a request interceptor // 添加请求前拦截器 axios.interceptors.request.use(function (config) { // Do something before request is sent return config; }, function (error) { // Do something with request error return Promise.reject(error); }); // Add a response interceptor // 添加请求后拦截器 axios.interceptors.response.use(function (response) { // Any status code that lie within the range of 2xx cause this function to trigger // Do something with response data return response; }, function (error) { // Any status codes that falls outside the range of 2xx cause this function to trigger // Do something with response error return Promise.reject(error); }); ``` 如果想把拦截器移除,可以用`eject`方法。 ```js const myInterceptor = axios.interceptors.request.use(function () {/*...*/}); axios.interceptors.request.eject(myInterceptor); ``` 拦截器也可以添加自定义的实例上。 ```js const instance = axios.create(); instance.interceptors.request.use(function () {/*...*/}); ``` 源码实现: 构造函数,`handles` 用于存储拦截器函数。 ```js function InterceptorManager() { this.handlers = []; } ``` 接下来声明了三个方法:使用、移除、遍历。 #### 4.3.1 InterceptorManager.prototype.use 使用 传递两个函数作为参数,数组中的一项存储的是`{fulfilled: function(){}, rejected: function(){}}`。返回数字 `ID`,用于移除拦截器。 ```js /** * @param {Function} fulfilled The function to handle `then` for a `Promise` * @param {Function} rejected The function to handle `reject` for a `Promise` * * @return {Number} 返回ID 是为了用 eject 移除 */ InterceptorManager.prototype.use = function use(fulfilled, rejected) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected }); return this.handlers.length - 1; }; ``` #### 4.3.2 InterceptorManager.prototype.eject 移除 根据 `use` 返回的 `ID` 移除 拦截器。 ```js /** * @param {Number} id The ID that was returned by `use` */ InterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null; } }; ``` 有点类似定时器`setTimeout` 和 `setInterval`,返回值是`id`。用`clearTimeout` 和`clearInterval`来清除定时器。 ```js // 提一下 定时器回调函数是可以传参的,返回值 timer 是数字 var timer = setInterval((name) => { console.log(name); }, 1000, '若川'); console.log(timer); // 数字 ID // 在控制台等会再输入执行这句,定时器就被清除了 clearInterval(timer); ``` #### 4.3.3 InterceptorManager.prototype.forEach 遍历 遍历执行所有拦截器,传递一个回调函数(每一个拦截器函数作为参数)调用,被移除的一项是`null`,所以不会执行,也就达到了移除的效果。 ```js /** * @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); } }); }; ``` ## 5. 实例结合 上文叙述的调试时运行`npm start` 是用`axios/sandbox/client.html`路径的文件作为示例的,读者可以自行调试。 以下是一段这个文件中的代码。 ```js axios(options) .then(function (res) { response.innerHTML = JSON.stringify(res.data, null, 2); }) .catch(function (res) { response.innerHTML = JSON.stringify(res.data, null, 2); }); ``` ### 5.1 先看调用栈流程 如果不想一步步调试,有个偷巧的方法。
知道 `axios` 使用了`XMLHttpRequest`。
可以在项目中搜索:`new XMLHttpRequest`。
定位到文件 `axios/lib/adapters/xhr.js`
在这条语句 `var request = new XMLHttpRequest();`
`chrome` 浏览器中 打个断点调试下,再根据调用栈来细看具体函数等实现。
`Call Stack` ```bash dispatchXhrRequest (xhr.js:19) xhrAdapter (xhr.js:12) dispatchRequest (dispatchRequest.js:60) Promise.then (async) request (Axios.js:54) wrap (bind.js:10) submit.onclick ((index):138) ``` 简述下流程:
1. `Send Request` 按钮点击 `submit.onclick`
2. 调用 `axios` 函数实际上是调用 `Axios.prototype.request` 函数,而这个函数使用 `bind` 返回的一个名为`wrap`的函数。
3. 调用 `Axios.prototype.request`
4. (有请求拦截器的情况下执行请求拦截器),中间会执行 `dispatchRequest`方法
5. `dispatchRequest` 之后调用 `adapter (xhrAdapter)`
6. 最后调用 `Promise` 中的函数`dispatchXhrRequest`,(有响应拦截器的情况下最后会再调用响应拦截器)
如果仔细看了文章开始的`axios 结构关系图`,其实对这个流程也有大概的了解。 接下来看 `Axios.prototype.request` 具体实现。 ### 5.2 Axios.prototype.request 请求核心方法 这个函数是核心函数。 主要做了这几件事: >1.判断第一个参数是字符串,则设置 url,也就是支持`axios('example/url', [, config])`,也支持`axios({})`。
>2.合并默认参数和用户传递的参数
>3.设置请求的方法,默认是是`get`方法
>4.将用户设置的请求和响应拦截器、发送请求的`dispatchRequest`组成`Promise`链,最后返回还是`Promise`实例。
也就是保证了请求前拦截器先执行,然后发送请求,再响应拦截器执行这样的顺序。
也就是为啥最后还是可以`then`,`catch`方法的缘故。
```js Axios.prototype.request = function request(config) { /*eslint no-param-reassign:0*/ // Allow for axios('example/url'[, config]) a la fetch API // 这一段代码 其实就是 使 axios('example/url', [, config]) // config 参数可以省略 if (typeof config === 'string') { config = arguments[1] || {}; config.url = arguments[0]; } else { config = config || {}; } // 合并默认参数和用户传递的参数 config = mergeConfig(this.defaults, config); // Set config.method // 设置 请求方法,默认 get 。 if (config.method) { config.method = config.method.toLowerCase(); } else if (this.defaults.method) { config.method = this.defaults.method.toLowerCase(); } else { config.method = 'get'; } // Hook up interceptors middleware // 组成`Promise`链 这段拆开到后文再讲述 }; ``` #### 5.2.1 组成`Promise`链,返回`Promise`实例 >这部分:用户设置的请求和响应拦截器、发送请求的`dispatchRequest`组成`Promise`链。也就是保证了请求前拦截器先执行,然后发送请求,再响应拦截器执行这样的顺序
也就是保证了请求前拦截器先执行,然后发送请求,再响应拦截器执行这样的顺序
也就是为啥最后还是可以`then`,`catch`方法的缘故。
如果读者对`Promise`不熟悉,建议读阮老师的书籍《ES6 标准入门》。 [阮一峰老师 的 ES6 Promise-resolve](http://es6.ruanyifeng.com/#docs/promise#Promise-resolve) 和 [JavaScript Promise迷你书(中文版)](http://liubin.org/promises-book/) ```js // 组成`Promise`链 // Hook up interceptors middleware // 把 xhr 请求 的 dispatchRequest 和 undefined 放在一个数组里 var chain = [dispatchRequest, undefined]; // 创建 Promise 实例 var promise = Promise.resolve(config); // 遍历用户设置的请求拦截器 放到数组的 chain 前面 this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); // 遍历用户设置的响应拦截器 放到数组的 chain 后面 this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); }); // 遍历 chain 数组,直到遍历 chain.length 为 0 while (chain.length) { // 两两对应移出来 放到 then 的两个参数里。 promise = promise.then(chain.shift(), chain.shift()); } return promise; ``` ```js var promise = Promise.resolve(config); ``` 解释下这句。作用是生成`Promise`实例。 ```js var promise = Promise.resolve({name: '若川'}) // 等价于 // new Promise(resolve => resolve({name: '若川'})) promise.then(function (config){ console.log(config) }); // {name: "若川"} ``` 同样解释下后文会出现的`Promise.reject(error);`: ```js Promise.reject(error); ``` ```js var promise = Promise.reject({name: '若川'}) // 等价于 // new Promise(reject => reject({name: '若川'})) // promise.then(null, function (config){ // console.log(config) // }); // 等价于 promise.catch(function (config){ console.log(config) }); // {name: "若川"} ``` 接下来结合例子,来理解这段代码。
很遗憾,在`example`文件夹没有拦截器的例子。笔者在`example`中在`example/get`的基础上添加了一个拦截器的示例。`axios/examples/interceptors`,便于读者调试。 ```bash node ./examples/server.js -p 5000 ``` `promise = promise.then(chain.shift(), chain.shift());`这段代码打个断点。 会得到这样的这张图。 ![request方法中promise链](./images/request-promise-chain.png) 特别关注下,右侧,`local`中的`chain`数组。也就是这样的结构。 ```js var chain = [ '请求成功拦截2', '请求失败拦截2', '请求成功拦截1', '请求失败拦截1', dispatch, undefined, '响应成功拦截1', '响应失败拦截1', '响应成功拦截2', '响应失败拦截2', ] ``` 这段代码相对比较绕。也就是会生成如下类似的代码,中间会调用`dispatchRequest`方法。 ```js // config 是 用户配置和默认配置合并的 var promise = Promise.resolve(config); promise.then('请求成功拦截2', '请求失败拦截2') .then('请求成功拦截1', '请求失败拦截1') .then(dispatchRequest, undefined) .then('响应成功拦截1', '响应失败拦截1') .then('响应成功拦截2', '响应失败拦截2') .then('用户写的业务处理函数') .catch('用户写的报错业务处理函数'); ``` 这里提下`promise` `then`和`catch`知识:
`Promise.prototype.then`方法的第一个参数是`resolved`状态的回调函数,第二个参数(可选)是`rejected`状态的回调函数。所以是成对出现的。
`Promise.prototype.catch`方法是`.then(null, rejection)`或`.then(undefined, rejection)`的别名,用于指定发生错误时的回调函数。
`then`方法返回的是一个新的`Promise`实例(注意,不是原来那个`Promise`实例)。因此可以采用链式写法,即`then`方法后面再调用另一个`then`方法。
结合上述的例子更详细一点,代码则是这样的。 ```js var promise = Promise.resolve(config); // promise.then('请求成功拦截2', '请求失败拦截2') promise.then(function requestSuccess2(config) { console.log('------request------success------2'); return config; }, function requestError2(error) { console.log('------response------error------2'); return Promise.reject(error); }) // .then('请求成功拦截1', '请求失败拦截1') .then(function requestSuccess1(config) { console.log('------request------success------1'); return config; }, function requestError1(error) { console.log('------response------error------1'); return Promise.reject(error); }) // .then(dispatchRequest, undefined) .then( function dispatchRequest(config) { /** * 适配器返回的也是Promise 实例 adapter = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) {}) } **/ return adapter(config).then(function onAdapterResolution(response) { // 省略代码 ... return response; }, function onAdapterRejection(reason) { // 省略代码 ... return Promise.reject(reason); }); }, undefined) // .then('响应成功拦截1', '响应失败拦截1') .then(function responseSuccess1(response) { console.log('------response------success------1'); return response; }, function responseError1(error) { console.log('------response------error------1'); return Promise.reject(error); }) // .then('响应成功拦截2', '响应失败拦截2') .then(function responseSuccess2(response) { console.log('------response------success------2'); return response; }, function responseError2(error) { console.log('------response------error------2'); return Promise.reject(error); }) // .then('用户写的业务处理函数') // .catch('用户写的报错业务处理函数'); .then(function (response) { console.log('哈哈哈,终于获取到数据了', response); }) .catch(function (err) { console.log('哎呀,怎么报错了', err); }); ``` 仔细看这段`Promise`链式调用,代码都类似。`then`方法最后返回的参数,就是下一个`then`方法第一个参数。
`catch`错误捕获,都返回`Promise.reject(error)`,这是为了便于用户`catch`时能捕获到错误。 举个例子: ```js var p1 = new Promise((resolve, reject) => { reject(new Error({name: '若川'})); }); p1.catch(err => { console.log(res, 'err'); return Promise.reject(err) }) .catch(err => { console.log(err, 'err1'); }) .catch(err => { console.log(err, 'err2'); }); ``` `err2`不会捕获到,也就是不会执行,但如果都返回了`return Promise.reject(err)`,则可以捕获到。 最后画个图总结下 `Promise` 链式调用。 ![axios promise 链式调用](./images/axios-promise-chain.png) >小结:1. 请求和响应的拦截器可以写`Promise`。
>2. 如果设置了多个请求响应器,后设置的先执行。
>3. 如果设置了多个响应拦截器,先设置的先执行。
`dispatchRequest(config)` 这里的`config`是请求成功拦截器返回的。接下来看`dispatchRequest`函数。 ### 5.3 dispatchRequest 最终派发请求 这个函数主要做了如下几件事情:
>1.如果已经取消,则 `throw` 原因报错,使`Promise`走向`rejected`。
>2.确保 `config.header` 存在。
>3.利用用户设置的和默认的请求转换器转换数据。
>4.拍平 `config.header`。
>5.删除一些 `config.header`。
>6.返回适配器`adapter`(`Promise`实例)执行后 `then`执行后的 `Promise`实例。返回结果传递给响应拦截器处理。
```js 'use strict'; // utils 工具函数 var utils = require('./../utils'); // 转换数据 var transformData = require('./transformData'); // 取消状态 var isCancel = require('../cancel/isCancel'); // 默认参数 var defaults = require('../defaults'); /** * 抛出 错误原因,使`Promise`走向`rejected` */ function throwIfCancellationRequested(config) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); } } /** * Dispatch a request to the server using the configured adapter. * * @param {object} config The config that is to be used for the request * @returns {Promise} The Promise to be fulfilled */ module.exports = function dispatchRequest(config) { // 取消相关 throwIfCancellationRequested(config); // Ensure headers exist // 确保 headers 存在 config.headers = config.headers || {}; // Transform request data // 转换请求的数据 config.data = transformData( config.data, config.headers, config.transformRequest ); // Flatten headers // 拍平 headers config.headers = utils.merge( config.headers.common || {}, config.headers[config.method] || {}, config.headers || {} ); // 以下这些方法 删除 headers utils.forEach( ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], function cleanHeaderConfig(method) { delete config.headers[method]; } ); // adapter 适配器部分 拆开 放在下文讲 }; ``` #### 5.3.1 dispatchRequest 之 transformData 转换数据 上文的代码里有个函数 `transformData` ,这里解释下。其实就是遍历传递的函数数组 对数据操作,最后返回数据。 `axios.defaults.transformResponse` 数组中默认就有一个函数,所以使用`concat`链接自定义的函数。 使用: 文件路径 `axios/examples/transform-response/index.html` 这段代码其实就是对时间格式的字符串转换成时间对象,可以直接调用`getMonth`等方法。 ```js var ISO_8601 = /(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})Z/; function formatDate(d) { return (d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear(); } axios.get('https://api.github.com/users/mzabriskie', { transformResponse: axios.defaults.transformResponse.concat(function (data, headers) { Object.keys(data).forEach(function (k) { if (ISO_8601.test(data[k])) { data[k] = new Date(Date.parse(data[k])); } }); return data; }) }) .then(function (res) { document.getElementById('created').innerHTML = formatDate(res.data.created_at); }); ``` 源码: 就是遍历数组,调用数组里的传递 `data` 和 `headers` 参数调用函数。 ```js module.exports = function transformData(data, headers, fns) { /*eslint no-param-reassign:0*/ utils.forEach(fns, function transform(fn) { data = fn(data, headers); }); return data; }; ``` #### 5.3.2 dispatchRequest 之 adapter 适配器执行部分 适配器,在设计模式中称之为适配器模式。讲个生活中简单的例子,大家就容易理解。 我们常用以前手机耳机孔都是圆孔,而现在基本是耳机孔和充电接口合二为一。统一为`typec`。 这时我们需要需要一个`typec转圆孔的转接口`,这就是适配器。 ```js // adapter 适配器部分 var adapter = config.adapter || defaults.adapter; return adapter(config).then(function onAdapterResolution(response) { throwIfCancellationRequested(config); // Transform response data // 转换响应的数据 response.data = transformData( response.data, response.headers, config.transformResponse ); return response; }, function onAdapterRejection(reason) { if (!isCancel(reason)) { // 取消相关 throwIfCancellationRequested(config); // Transform response data // 转换响应的数据 if (reason && reason.response) { reason.response.data = transformData( reason.response.data, reason.response.headers, config.transformResponse ); } } return Promise.reject(reason); }); ``` 接下来看具体的 `adapter`。 ### 5.4 adapter 适配器 真正发送请求 ```js var adapter = config.adapter || defaults.adapter; ``` 看了上文的 `adapter`,可以知道支持用户自定义。比如可以通过微信小程序 `wx.request` 按照要求也写一个 `adapter`。
接着来看下 `defaults.ddapter`。
文件路径:`axios/lib/defaults.js` 根据当前环境引入,如果是浏览器环境引入`xhr`,是`node`环境则引入`http`。
类似判断`node`环境,也在[`sentry-javascript`](https://github.com/getsentry/sentry-javascript/blob/a876d46c61e2618e3c3a3e1710f77419331a9248/packages/utils/src/misc.ts#L37-L40)源码中有看到。
```js function getDefaultAdapter() { var adapter; // 根据 XMLHttpRequest 判断 if (typeof XMLHttpRequest !== 'undefined') { // For browsers use XHR adapter adapter = require('./adapters/xhr'); // 根据 process 判断 } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // For node use HTTP adapter adapter = require('./adapters/http'); } return adapter; } var defaults = { adapter: getDefaultAdapter(), // ... }; ``` `xhr` 接下来就是我们熟悉的 `XMLHttpRequest` 对象。 可能读者不了解可以参考[XMLHttpRequest MDN 文档](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest)。 主要提醒下:`onabort`是请求取消事件,`withCredentials`是一个布尔值,用来指定跨域 `Access-Control` 请求是否应带有授权信息,如 `cookie` 或授权 `header` 头。 这块代码有删减,具体可以看[若川的`axios-analysis`仓库](https://github.com/lxchuan12/axios-analysis/blob/master/axios/lib/adapters/xhr.js),也可以克隆笔者的`axios-analysis`仓库调试时再具体分析。 ```js module.exports = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { // 这块代码有删减 var request = new XMLHttpRequest(); request.open() request.timeout = config.timeout; // 监听 state 改变 request.onreadystatechange = function handleLoad() { if (!request || request.readyState !== 4) { return; } // ... } // 取消 request.onabort = function(){}; // 错误 request.onerror = function(){}; // 超时 request.ontimeout = function(){}; // cookies 跨域携带 cookies 面试官常喜欢考这个 // 一个布尔值,用来指定跨域 Access-Control 请求是否应带有授权信息,如 cookie 或授权 header 头。 // Add withCredentials to request if needed if (!utils.isUndefined(config.withCredentials)) { request.withCredentials = !!config.withCredentials; } // 上传下载进度相关 // Handle progress if needed if (typeof config.onDownloadProgress === 'function') { request.addEventListener('progress', config.onDownloadProgress); } // Not all browsers support upload events if (typeof config.onUploadProgress === 'function' && request.upload) { request.upload.addEventListener('progress', config.onUploadProgress); } // Send the request // 发送请求 request.send(requestData); }); } ``` 而实际上现在 [`fetch`](https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch) 支持的很好了,阿里开源的 [umi-request](https://github.com/umijs/umi-request/blob/master/README_zh-CN.md) 请求库,就是用`fetch`封装的,而不是用`XMLHttpRequest`。 文章末尾,大概讲述下 `umi-request` 和 `axios` 的区别。 `http` `http`这里就不详细叙述了,感兴趣的读者可以自行查看,[若川的`axios-analysis`仓库](https://github.com/lxchuan12/axios-analysis/blob/master/axios/lib/adapters/http.js)。 ```js module.exports = function httpAdapter(config) { return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) { }); }; ``` 上文 `dispatchRequest` 有取消模块,我觉得是重点,所以放在最后来细讲: ### 5.5 dispatchRequest 之 取消模块 可以使用`cancel token`取消请求。 axios cancel token API 是基于撤销的 `promise` 取消提议。 The axios cancel token API is based on the withdrawn [cancelable promises proposal.](https://github.com/tc39/proposal-cancelable-promises) [axios 文档 cancellation](https://github.com/axios/axios#cancellation) 文档上详细描述了两种使用方式。 很遗憾,在`example`文件夹也没有取消的例子。笔者在`example`中在`example/get`的基础上添加了一个取消的示例。`axios/examples/cancel`,便于读者调试。 ```bash node ./examples/server.js -p 5000 ``` `request`中的拦截器和`dispatch`中的取消这两个模块相对复杂,可以多调试调试,吸收消化。 ```js const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios.get('/get/server', { cancelToken: source.token }).catch(function (err) { if (axios.isCancel(err)) { console.log('Request canceled', err.message); } else { // handle error } }); // cancel the request (the message parameter is optional) // 取消函数。 source.cancel('哎呀,我被若川取消了'); ``` #### 5.5.1 取消请求模块代码示例 结合源码取消流程大概是这样的。这段放在代码在`axios/examples/cancel-token/index.html`。 参数的 `config.cancelToken` 是触发了`source.cancel('哎呀,我被若川取消了');`才生成的。 ```js // source.cancel('哎呀,我被若川取消了'); // 点击取消时才会 生成 cancelToken 实例对象。 // 点击取消后,会生成原因,看懂了这段在看之后的源码,可能就好理解了。 var config = { name: '若川', // 这里简化了 cancelToken: { promise: new Promise(function(resolve){ resolve({ message: '哎呀,我被若川取消了'}) }), reason: { message: '哎呀,我被若川取消了' } }, }; // 取消 抛出异常方法 function throwIfCancellationRequested(config){ // 取消的情况下执行这句 if(config.cancelToken){ // 这里源代码 便于执行,我改成具体代码 // config.cancelToken.throwIfRequested(); // if (this.reason) { // throw this.reason; // } if(config.cancelToken.reason){ throw config.cancelToken.reason; } } } function dispatchRequest(config){ // 有可能是执行到这里就取消了,所以抛出错误会被err2 捕获到 throwIfCancellationRequested(config); // adapter xhr适配器 return new Promise((resovle, reject) => { var request = new XMLHttpRequest(); console.log('request', request); if (config.cancelToken) { // Handle cancellation config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); // Clean up request request = null; }); } }) .then(function(res){ // 有可能是执行到这里就才取消 取消的情况下执行这句 throwIfCancellationRequested(config); console.log('res', res); return res; }) .catch(function(reason){ // 有可能是执行到这里就才取消 取消的情况下执行这句 throwIfCancellationRequested(config); console.log('reason', reason); return Promise.reject(reason); }); } var promise = Promise.resolve(config); // 没设置拦截器的情况下是这样的 promise .then(dispatchRequest, undefined) // 用户定义的then 和 catch .then(function(res){ console.log('res1', res); return res; }) .catch(function(err){ console.log('err2', err); return Promise.reject(err); }); // err2 {message: "哎呀,我被若川取消了"} ``` #### 5.5.2 接下来看取消模块的源码 看如何通过生成`config.cancelToken`。 文件路径: `axios/lib/cancel/CancelToken.js` ```js const CancelToken = axios.CancelToken; const source = CancelToken.source(); source.cancel('哎呀,我被若川取消了'); ``` 由示例看 `CancelToken.source`的实现, ```js CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); // token return { token: token, cancel: cancel }; }; ``` 执行后`source`的大概结构是这样的。 ```js { token: { promise: new Promise(function(resolve){ resolve({ message: '哎呀,我被若川取消了'}) }), reason: { message: '哎呀,我被若川取消了' } }, cancel: function cancel(message) { if (token.reason) { // Cancellation has already been requested // 已经取消 return; } token.reason = {message: '哎呀,我被若川取消了'}; } } ``` 接着看 `new CancelToken` ```js // CancelToken // 通过 CancelToken 来取消请求操作 function CancelToken(executor) { if (typeof executor !== 'function') { throw new TypeError('executor must be a function.'); } var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { resolvePromise = resolve; }); var token = this; executor(function cancel(message) { if (token.reason) { // Cancellation has already been requested // 已经取消 return; } token.reason = new Cancel(message); resolvePromise(token.reason); }); } module.exports = CancelToken; ``` 发送请求的适配器里是这样使用的。 ```js // xhr if (config.cancelToken) { // Handle cancellation config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); reject(cancel); // Clean up request request = null; }); } ``` `dispatchRequest` 中的`throwIfCancellationRequested`具体实现:throw 抛出异常。 ```js // 抛出异常函数 function throwIfCancellationRequested(config) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); } } // 抛出异常 用户 { message: '哎呀,我被若川取消了' } CancelToken.prototype.throwIfRequested = function throwIfRequested() { if (this.reason) { throw this.reason; } }; ``` 取消流程调用栈 >1.source.cancel()
>2.resolvePromise(token.reason);
>3.config.cancelToken.promise.then(function onCanceled(cancel) {})
最后进入`request.abort();``reject(cancel);` 到这里取消的流程就介绍完毕了。主要就是通过传递配置参数`cancelToken`,取消时才会生成`cancelToken`,判断有,则抛出错误,使`Promise` 走向`rejected`,让用户捕获到消息{message: '用户设置的取消信息'}。 文章写到这里就基本到接近尾声了。 能读到最后,说明你已经超过很多人啦^_^ `axios`是非常优秀的请求库,但肯定也不能满足所有开发者的需求,接下来对比下其他库,看看其他开发者有什么具体需求。 ## 6. 对比其他请求库 ### 6.1 KoAjax FCC成都社区负责人水歌开源的[KoAJAX](https://github.com/EasyWebApp/KoAJAX)。 [如何用开源软件办一场技术大会?](https://mp.weixin.qq.com/s/hxCwiokl4uPXJscTQi42-A) 以下这篇文章中摘抄的一段。 >前端请求库 —— KoAJAX >国内前端同学最常用的 HTTP 请求库应该是 axios 了吧?虽然它的 Interceptor(拦截器)API 是 .use(),但和 Node.js 的 Express、Koa 等框架的中间件模式完全不同,相比 jQuery .ajaxPrefilter()、dataFilter() 并没什么实质改进;上传、下载进度比 jQuery.Deferred() 还简陋,只是两个专门的回调选项。所以,它还是要对特定的需求记忆特定的 API,不够简洁。 >幸运的是,水歌在研究如何[用 ES 2018 异步迭代器实现一个类 Koa 中间件引擎](https://tech-query.me/onion-stack/)的过程中,做出了一个更有实际价值的上层应用 —— KoAJAX。它的整个执行过程基于 Koa 式的中间件,而且它自己就是一个中间件调用栈。除了 RESTful API 常用的 .get()、.post()、.put()、.delete() 等快捷方法外,开发者就只需记住 .use() 和 next(),其它都是 ES 标准语法和 TS 类型推导。 ### 6.2 umi-request 阿里开源的请求库 [umi-request github 仓库](https://github.com/umijs/umi-request/blob/master/README_zh-CN.md) `umi-request` 与 `fetch`, `axios` 异同。 ![`umi-request` 与 `fetch`, `axios` 异同](./images/umi-request-image.png) 不得不说,`umi-request` 确实强大,有兴趣的读者可以阅读下其源码。 看懂`axios`的基础上,看懂`umi-request`源码应该不难。 比如 `umi-request` 取消模块代码几乎与`axios`一模一样。 ## 7. 总结 文章详细介绍了 `axios` 调试方法。详细介绍了 `axios` 构造函数,拦截器,取消等功能的实现。最后还对比了其他请求库。 最后画个图总结一下axios的总体大致流程。 ![axios的总体大致流程](./images/axios-all.png) 解答下文章开头提的问题: 如果你是求职者,项目写了运用了`axios`,面试官可能会问你: >1.为什么 `axios` 既可以当函数调用,也可以当对象使用,比如`axios({})`、`axios.get`。
答:`axios`本质是函数,赋值了一些别名方法,比如`get`、`post`方法,可被调用,最终调用的还是`Axios.prototype.request`函数。
>2.简述 `axios` 调用流程。
答:实际是调用的`Axios.prototype.request`方法,最终返回的是`promise`链式调用,实际请求是在`dispatchRequest`中派发的。
>3.有用过拦截器吗?原理是怎样的?
答:用过,用`axios.interceptors.request.use`添加请求成功和失败拦截器函数,用`axios.interceptors.response.use`添加响应成功和失败拦截器函数。在`Axios.prototype.request`函数组成`promise`链式调用时,`Interceptors.protype.forEach`遍历请求和响应拦截器添加到真正发送请求`dispatchRequest`的两端,从而做到请求前拦截和响应后拦截。拦截器也支持用`Interceptors.protype.eject`方法移除。
>4.有使用`axios`的取消功能吗?是怎么实现的?
答:用过,通过传递`config`配置`cancelToken`的形式,来取消的。判断有传`cancelToken`,在`promise`链式调用的`dispatchRequest`抛出错误,在`adapter`中`request.abort()`取消请求,使`promise`走向`rejected`,被用户捕获取消信息。
>5.为什么支持浏览器中发送请求也支持`node`发送请求?
答:`axios.defaults.adapter`默认配置中根据环境判断是浏览器还是`node`环境,使用对应的适配器。适配器支持自定义。
回答面试官的问题,读者也可以根据自己的理解,组织语言,笔者的回答只是做一个参考。 `axios` 源码相对不多,打包后一千多行,比较容易看完,非常值得学习。 建议 `clone` [若川的 axios-analysis github 仓库](https://github.com/lxchuan12/axios-analysis),按照文中方法自己调试,印象更深刻。
基于`Promise`,构成`Promise`链,巧妙的设置请求拦截,发送请求,再试试响应拦截器。 `request`中的拦截器和`dispatch`中的取消这两个模块相对复杂,可以多调试调试,吸收消化。 `axios` 既是函数,是函数时调用的是`Axios.prototype.request`函数,又是对象,其上面有`get`、`post`等请求方法,最终也是调用`Axios.prototype.request`函数。 `axios` 源码中使用了挺多设计模式。比如工厂模式、迭代器模式、适配器模式等。如果想系统学习设计模式,一般比较推荐豆瓣评分9.1的[JavaScript设计模式与开发实践](https://book.douban.com/subject/26382780/) 如果读者发现有不妥或可改善之处,再或者哪里没写明白的地方,欢迎评论指出。另外觉得写得不错,对您有些许帮助,可以点赞、评论、转发分享,也是对笔者的一种支持,非常感谢呀。 ## 推荐阅读 [官方axios github 仓库](https://github.com/axios/axios)
写文章前,搜索了以下几篇文章泛读了一下。有兴趣再对比看看以下这几篇,有代码调试的基础上,看起来也快。 一直觉得多搜索几篇文章看,对自己学习知识更有用。有个词语叫主题阅读。大概意思就是一个主题一系列阅读。 [@叫我小明呀:Axios 源码解析](https://juejin.im/post/5cb5d9bde51d456e62545abc)
[@尼库尼库桑:深入浅出 axios 源码](https://zhuanlan.zhihu.com/p/37962469)
[@小贼先生_ronffy:Axios源码深度剖析 - AJAX新王者](https://juejin.im/post/5b0ba2d56fb9a00a1357a334)
[逐行解析Axios源码](https://juejin.im/post/5d501512518825159e3d7be6)
[[译]axios 是如何封装 HTTP 请求的](https://juejin.im/post/5d906269f265da5ba7451b02)
[知乎@Lee : TypeScript 重构 Axios 经验分享](https://zhuanlan.zhihu.com/p/50859466)
## 笔者另一个系列 [面试官问:JS的继承](https://juejin.im/post/5c433e216fb9a049c15f841b)
[面试官问:JS的this指向](https://juejin.im/post/5c0c87b35188252e8966c78a)
[面试官问:能否模拟实现JS的call和apply方法](https://juejin.im/post/5bf6c79bf265da6142738b29)
[面试官问:能否模拟实现JS的bind方法](https://juejin.im/post/5bec4183f265da616b1044d7)
[面试官问:能否模拟实现JS的new操作符](https://juejin.im/post/5bde7c926fb9a049f66b8b52)
## 关于 作者:常以**若川**为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。
[若川的博客](https://lxchuan12.gitee.io),使用`vuepress`重构了,阅读体验可能更好些
[掘金专栏](https://juejin.cn/user/1415826704971918/posts),欢迎关注~
[`segmentfault`前端视野专栏](https://segmentfault.com/blog/lxchuan12),欢迎关注~
[知乎前端视野专栏](https://zhuanlan.zhihu.com/lxchuan12),欢迎关注~
[语雀前端视野专栏](https://www.yuque.com/lxchuan12/blog),新增语雀专栏,欢迎关注~
[github blog](https://github.com/lxchuan12/blog),相关源码和资源都放在这里,求个`star`^_^~ ## 欢迎加微信交流 微信公众号 可能比较有趣的微信公众号,长按扫码关注(**回复pdf获取前端优质书籍pdf**)。欢迎加笔者微信`ruochuan12`(注明来源,基本来者不拒),拉您进【前端视野交流群】,长期交流学习~ ![若川视野](https://github.com/lxchuan12/blog/raw/master/docs/about/wechat-official-accounts-mini.jpg)