从vue2版本开始,vue-resource就不再被vue所维护和支持,官方也推荐使用axios,所以,从我使用axios至今,差不多有四五年了,这四五年的时间只能算是熟练应用,很多内部的实现和原理不清不楚,导致在开发的时候遇到问题,大多数情况都是凭借经验来“猜测”出答案,这就导致内心深处十分的空虚和忐忑,就像是走路的时候脚步虚浮,跌跌撞撞,一点都不平稳。刚好最近的计划是看源码,所以就从axios开始,详细的去解读整个axios的实现,希望这个系列既是笔记也是分享,让大家知道原理,理解场景,懂得实现。ok,下面我们开始进入正文吧。
axios本身的核心其实就是XMLHttpRequest,但是又不仅仅是XMLHttpRequest。简单来说,一个库或者一个框架,一个项目,它的核心内容都包含了两部分:打包构建和核心源码。那么本系列对打包构建部分一带而过,只是提供了可以调试源码的程度,并不会对打包构建说太多,一方面是自认为水平还达不到对构建也通透的程度,另外一方面,希望可以抽丝剥茧,去繁取简,让源码不再是神秘的、不可触及的,让所有程度的前端同学,都可以从这系列中学到、用到、得到。
哈哈,说多了,下面继续说axios吧。刚才说到了一个项目包含两部分:打包构建和核心源码,那么在核心源码里axios还可以继续拆分出:js代码、typescript声明、单元测试、demo例子。我们这个系列,仅实现:轻量的打包、demo例子和js源码三个部分。当然,或许后续有时间的话,还会把typescript和单元测试、打包构建也都聊一下,不过,那或许得等我学会的时候啦。哈哈。
本系列会在每篇文章中,以axios的api入手,对比原生的XMLHttpRequest,会事先聊一下要实现的axiso API是如何使用的,然后根据该部分内容,逐步实现axios源码。
其次,我还会在gitHub上发布实现的源码,每个章节都对应一个分支,可以具体看到代码的逐步迭代过程。针对每一个章节,深入的学习,另外,虽然我实现的代码尽可能的贴近axios的源码,甚至有一些工具方法,都是完美复制的。但是,在大家看完本系列文章后,我还是建议大家去看看真正的源码,自己fork一份,对比阅读学习。
一、axios项目结构及生态简介
1、axios打包
我们先来看下axios完整的目录结构,每一个文件的含义介绍在CATALOG.md中,大家可以去看下,在这里仅抽出一部分核心的内容说下。
首先整个axios项目的打包构建使用了Grunt,通过Grunt配置一些流程操作,比如单元测试,打包等流程,Grunt算是整个项目构建的流程管控工具。其次,单元测试是用的mocha+karma的体系。然后打包,最终生成包结果是使用了webpack。其他的细节不多说,这不是本系列的重点。过~~
2、axios及其生态
我们可以回顾整个axios的Tags,从最初的0.1.0版本到现在的0.25.0,整个项目的流程管理工具、单元测试工具等,都没有变化,只是在逐渐迭代的过程中加入了typescript声明,单元测试等。从功能上来说,最开始的axios其实是angular生态的一个模块,只有简单的请求方法,并没有现在的cancelToken,interceptor等功能,随着时代的变化,逐步分离出来成为独立的ajax库。更为详细更新过程的大家可以去看axios的UPGRADE_GUIDE.md文档。另外要说一下的是axios的生态,有很多可以配合axios使用的工具,详情可以去axios的ECOSYSTEM.md文档查看。
其实上面说的都是屁话,毛用没有~~~
二、ajax及其相关
这小节我们来聊下客户端与服务器通信的方式有哪些,着重介绍下ajax以及XMLHttpRequest,额外的,还会简单介绍下WebSockets、EventSource、fetch等。ajax和XMLHttpRequest是重点,重点来了!!!
1、ajax和XMLHttpRequest
众所周知,ajax的全称是Asynchronous JavaScript and XML,即异步的Javascript和XML,通过使用ajax可以使用js来发送请求,服务器返回的数据再通过前端js代码,来渲染到页面上。ajax本身并不是一项新技术,而是一些技术的集合。那么,在开始了解ajax之前,假如没有ajax,客户端如何与服务器交互呢?
首先,可以通过iframe,其次还有表单提交,超链接等方式。或者,比较传统的可以通过jsp等后端语言技术来实现。但是,客户端与服务器通信的目的我们实现了,但是有一个核心的问题仍旧无法解决,也就是异步。每一次的表单提交,超链接等,都要刷新整个页面,导致我们的交互体验并不是十分友好。所以,ajax的出现,解决了部分数据刷新的问题,使得数据的获取和局部渲染变得更为便捷。
上面说道,ajax并不是一个新的技术,而是几种技术的组合,那么其中最为核心的就是XMLHttpRequest。具体的XMLHttpRequest文档可以参考MDN。这里不再多说。
以下是一个最简单的XMLHttpRequest请求例子,我们通过这个简单的例子,来看看XMLHttpRequest的一些相关api,这是我们后续实现axios的基础,首先,我们在本地创建一个html文件,代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body></body> <script> const data = { a: 1, b: 2 }; var request = new XMLHttpRequest(); request.open("POST", "http://httpbin.org/post?a=1&b=2"); request.send(data); </script> </html>
我想来想去,核心的代码就这些,很简单,其实就三行代码,但是我们却可以来分析下这三行代码。首先我们创建一个XMLHttpRequest对象,然后通过这个对象实例,调用open方法,然后再调用send方法。那么第一个问题就是,如何拼接url的get请求的query参数?我们知道axios是传入的params对象,所以这就是我要实现的源码之一,再然后,data是个对象,但是body的请求体接收的是一个json字符串,所以我们也要转换。到了这里我们简单的了解了XMLHttpRequest的核心基础API。那么下面我们结合rollup打包工具,来生成一个我们写好的ajax请求的例子。
要注意,这个例子只是一个简单的XMLHttpRequest对象的应用,和axios无关又有关。rollup打包的代码就十来行,大家可以在c0分支中的rollup.config.js中查看,有兴趣的可以关注下,没兴趣的可以不用关注,直接把项目npm run build就可以了。
然后我们在dist的目录下,创建个index.html,内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="./axios.umd.js"></script>
<script>
zakingAxios();
</script>
</body>
</html>
其实就是引入我们打包后的文件,然后调用。然后打开index.html文件,就可以看到打印出来的axios字符串了。哦对了,lib下的axios文件中的代码是这样的:
function axios() { console.log("axios"); } export default axios;
OK。那么下面我们就来写一个例子。哦对,我们请求的接口来自于这个地址:https://httpbin.org/。我们先来看下代码:
function axios() { const xhr = new XMLHttpRequest(); xhr.open("GET", "https://httpbin.org/get"); xhr.send(); } export default axios;
然后npm run build重新打包下就可以在控制台看到get请求了。但是这只是最简单的get请求,那我们来增加一点需求。我希望可以给get请求传参数,怎么办?
xhr.open("GET", "https://httpbin.org/get?a=1&b=1&c=1");
那,我用get请求是否可以传递数组和对象呢?ok,这是我们这篇文章留下的第一个问题。跳过,我们继续来增加需求,现在get请求传参数可以了,我想用post请求并且传递个对象,咋整?这是我们在开发中最常见的场景了。我们把代码改一下:
function axios() { const xhr = new XMLHttpRequest(); xhr.open("POST", "https://httpbin.org/post"); xhr.send({ a: 1, b: 2 }); } export default axios;
跑起来后我们发现一个问题。诶?怎么会这样?
XMLHttpRequest是不接受对象形式的body的,那么我们把它转换成JSON字符串呢?
xhr.send(JSON.stringify({ a: 1, b: 2 }));
我们发现可以了~~那么接下来,我希望可以收到响应的body咋办呢?
function axios() { const xhr = new XMLHttpRequest(); xhr.open("POST", "https://httpbin.org/post"); xhr.send(JSON.stringify({ a: 1, b: 2 })); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { console.log(xhr.response); console.log(xhr.responseText); } }; } export default axios;
我们可以通过判断XMLHttpRequest实例对象上的readyState和status来判断请求是否结束,然后获取xhr上的response或者responseText。那么,第二个问题,response和responseText有啥区别?问题的答案可以在这里寻找:https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest。关于readyState和status也都可以在这个链接找到,或者说,关于XMLHttpRequest相关的方法和属性都可以在这里查询。
OK,我们完整的发起了一个POST请求,例子就到此为止,深入的内容我们会在后面的章节实现axios的时候再详细介绍。点到为止。
2、EventSource
EventSource可以让服务器主动发送数据到我们的代码中, 当不需要以消息形式将数据从客户端发送到服务器时,这使它们成为绝佳的选择。例如,对于处理社交媒体状态更新,新闻提要或将数据传递到客户端存储机制(如IndexedDB或Web存储)之类的,EventSource无疑是一个有效方案(这段话是抄的)。具体内容可查看MDN。
3、WebSockets
这个东西相信大家也有一定的了解,它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此API,您可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应,可参考MDN。
4、ActiveXObject
这个东西有点陌生,而且有点复杂, 它可以操作文件、文件夹,获取相关信息,发起http请求等,它是一个复杂的功能庞大的对象或者说接口,http请求功能只不过是它的一小部分,发起请求可以通过如下的形式,在之前IE兼容的时候,如果没有XMLHttpRequest,也会使用到ActiveXObject:
new window.ActiveXObject("Mscrosoft.XMLHttp")
这个也简单提一下,过~~,有兴趣自行百度!
4、Fetch
这个东西想必大家都比较熟悉,或多或少听说过,算是XMLHttpRequest的升级版,也是用来在浏览器中发起http请求。fetch是用了promise,简洁了用法。并且采用模块化设计,api分散在多个对象上,如果要展开的话内容很多,所以大家可以去本章的参考资料中查看,阮一峰大神写的很好了,这里也不多说。链接贴在了最后。
三、目录
- 一比一还原axios源码(一)—— 发起第一个请求
- 一比一还原axios源码(二)—— 请求响应处理
- 一比一还原axios源码(三)—— 错误处理
- 一比一还原axios源码(四)—— Axios类
- 一比一还原axios源码(五)—— 拦截器
- 一比一还原axios源码(六)—— 配置化
- 一比一还原axios源码(七)—— 取消功能
- 一比一还原axios源码(八)—— 其他功能
四、阶段闲聊
终于,写完了整个axios的实现,写完了之后发现其实axios的实现源码并不是十分复杂,但是需要一定的了解和熟悉,需要一定的阅读理解能力。我就不多说了,最后说说这一系列的文章吧。首先,整个zaking-axios的example部分的代码,来源于慕课网《下一代前端开发语言 TypeScript从零重构axios》这个视频课,ustbhuangyi老师讲的真的很好。我在学习的过程中就想自己实现一遍,但是课程中有些代码是经过老师优化的,并且加上了ts、jest等。一方面由于我的水平有限,另外一方面我又希望可以让初学者更容易学习,所以,整个zaking-axios并没有加那么多,你只要有基本的js基础,就能看懂我写的zaking-axios。当然,或许后面时间充足,我也会加上ts和jest,也会尽我所能的去优化其中的代码。
最后,希望大家在阅读的过程中有什么疑问或者发现了什么问题,都可以及时交流,我也会尽快回复。
最后的最后,感谢!
参考资料,附: