上一篇文章,我们简单介绍了XMLHttpRequest及其他可以发起AJAX请求的API,那部分大家有兴趣可以自己去扩展学习。另外,简单介绍了怎么去读以及我会怎么写这个系列的文章,那么下面就开始真正的axios源码实现,跟紧我的步伐,你会发现其实阅读源码并不是一件很复杂的事情。另外,我在上一篇概要中附上的链接,大家一定要去看,至少要了解一下XMLHttpRequest的相关属性和方法都有哪些,因为接下来的核心内容,其实都是基于此的。

  那么先来看看我们今天要来实现的内容有哪些,首先第一部分,我会创建一个本地的server服务,实现这部分的代码,以供我们在实现axios的过程中可以用来验证代码以做测试,另外,会实现简单的axios的get请求。

  下面我们就先来看看server的代码是什么样的。

一、编写server代码

  首先,我们在examples文件夹下创建webpack.config.js和server.js文件,是server部分的核心代码,其中webpack比较简单,代码如下:

const fs = require("fs");
const path = require("path");
const webpack = require("webpack"); module.exports = {
mode: "development",
entry: fs.readdirSync(__dirname).reduce((entries, dir) => {
const fullDir = path.join(__dirname, dir);
const entry = path.join(fullDir, "app.js");
if (fs.statSync(fullDir).isDirectory() && fs.existsSync(entry)) {
entries[dir] = ["webpack-hot-middleware/client", entry];
}
return entries;
}, {}),
output: {
path: path.join(__dirname, "__build__"),
filename: "[name].js",
publicPath: "/__build__/",
},
module: {},
resolve: {
extensions: [".js"],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
],
};

  看上面的代码,核心就是读取examples目录下的所有文件,然后生成多页应用。其中if判断的是如果是文件夹并且该文件夹存在,那么则会加入热更新的依赖。这些都比较容易理解。其次,是server.js。

  通过express生成一个服务器,并读取webpack.config.js的配置文件, express通过webpack-dev-middleware插件来读取webpack的配置文件,最后通过

app.use(express.static(__dirname));

  这行代码,读取根目录下的index.html作为访问服务器的跟路由页面。再然后通过下面的代码注册每一个example的路由,这里的路由,是后端路由,代表着可访问的接口地址:

function registerC1Router() {
router.get("/c1/get", function (req, res) {
res.json({
msg: `hello world`,
});
});
}

  在后面的开发过程中,会根据章节增加examples的demo代码。完整的代码大家也可以在https://github.com/zakingWong/zaking-axios/tree/c1这里查看。

二、发起ajax请求

  接下来,我们要看如何实现axios中的一个api,我们先看下axios的官方文档:

  这是axios从服务器获取一个图片的方法,发起了get请求,需要一个url,那么我们今天就来实现红框中的部分,通过代码的实现,来发起一个真正的请求。

  接下来,我们在lib文件夹里创建一个adapters文件夹,在adapters文件夹下创建一个xhr文件,这是我们真正的XMLHttpRequest的代码,xhr文件的代码这样写:

export default function xhrAdapter(config) {
var request = new XMLHttpRequest(); request.open(config.method.toUpperCase(), config.url, true); request.send(config.data);
}

  很简单,实际上就是开启一个XMLHttpRequest请求。然后在lib下的axios文件中引入并调用即可。这样,我们就完成了axios源码的实现,好了,本系列到此结束。哈哈哈,开个玩笑。

1、完善url参数

  OK,经过上面的代码,我们已经可以发起get请求了,但是还有个问题没有解决,就是params参数的传递,axios可以传递params后拼在url的请求后面。那么,我们也来实现一下。axios通过一个buildURL方法来辅助处理url后携带的参数,那么我们也照着抄呗。

  首先,我们在lib下创建一个helpers文件夹,这个文件夹是用来放一堆一堆的辅助处理的方法的,在这个文件夹下,我们创建一个名字叫buildURL的文件。然后,我们一口气把目前所需要的文件创建完吧,接下来就可以专注的写代码了,我们再在lib文件夹下创建一个utils文件,也就是用来存放一些工具方法,我们稍后会用到的时候再来抄,哦不,再来写。

  下面……激动人心的时刻到了,但是我们还不能开始写代码,我们先来看看。我们需要处理params的场景有哪些:

最常见的普通使用方法:

axios({
method: "get",
url: "/c1/get",
params: {
a: 1,
b: 2,
},
});

  上面这种场景是最常见的,我们希望可以把params对象拼在url后面,变成这样:"/c1/get?a=1&b=2"即可。

参数值为数组:

axios({
method: "get",
url: "/c1/get",
params: {
a: [1, 2, 3, 4],
},
});

  上面的代码,params参数对象的值a是一个数组,我们希望它的url可以变成这样:"/c1/get?a[]=1&a[]=2&a[]=3&a[]=4",就好了。

参数值为对象:

// params的参数的值为对象的情况
axios({
method: "get",
url: "/c1/get",
params: {
a: {
b: 1,
},
},
});

   我们希望是这样的url:"/c1/get?a=%7B%22b%22:%221%22%7D"。这里的%7B就是“{”,%22就是“"”,%7D就是“}”,也就是字符encode后的结果。

参数值为Date类型:

// params的参数的值为Date类型
const date = new Date(); axios({
method: "get",
url: "/c1/get",
params: {
date,
},
});

  它在url上就会变成这样:"/c1/get?date=2019-04-01T05:55:39.030Z"date 后面拼接的是 date.toISOString() 的结果。

特殊字符的支持:

// 支持特殊字符
axios({
method: "get",
url: "/c1/get",
params: {
a: "@:$, ",
},
});

  我们希望可以支持@:$,[],这些字符@符号,:冒号,,逗号,空格,中括号[],$美元符,希望这些特殊符号不会被encode,要注意,这里的空格会被转换成+号。url就是这样的:"/c1/get?foo=@:$+"。

忽略空值

// 忽略空值
axios({
method: "get",
url: "/c1/get",
params: {
a: 1,
b: null,
c: undefined,
},
});

  所以上面的请求代码,url就是这样:"/c1/get?a=1"。

丢弃URL中的hash标记

// 丢弃URL中的hash标记
axios({
method: "get",
url: "/c1/get#fuckhash",
params: {
a: 1,
b: null,
c: undefined,
},
});

  诶?这个跟上面的url表现是一样的:"/c1/get?a=1"。

保留URL中已存在的参数

// 保留URL中已存在的参数
axios({
method: "get",
url: "/c1/get#fuckhash?m=12&md=23",
params: {
a: 1,
b: null,
c: undefined,
},
});

  这个呢就是这样的:"/c1/get?m=12&md=23&a=1"。

  OK,终于,我们分析完了所有的情况,下面就要开始抄写buildURL代码了。

export default function buildURL(url, params, paramsSerializer) {
// 如果没有params的参数的话,直接返回url即可
if (!params) return url;
// 首先啊,由于我们引入了可以自定义转换的逻辑,所以这里我们先判断一下
let serializedParams; // 这个变量就是转换后的url参数
if (paramsSerializer) {
serializedParams = paramsSerializer(params);
} else if (utils.isURLSearchParams(params)) {
serializedParams = params.toString();
} else {
// 如果即没有自定义的转换方法,又不是一个URLSearchParams对象,那么就走默认的转换逻辑
// 先声明一个存储变量
let parts = [];
// 这里用了一个自定义的循环方法
utils.forEach(params, function serialize(val, key) {
// 这个跟我们说好的场景一致,如果没有值,就不管它了。
if (val === null || typeof val === "undefined") {
return;
} // 判断val是否是个数组,如果是数组的话,那么key要变化一下,这个咱们之前的需求也说过,
// 如果不是的话,把它变成数组,方便后面统一循环处理
if (utils.isArray(val)) {
key = key + "[]";
} else {
val = [val];
} utils.forEach(val, function parseValue(v) {
// 这个也说了,日期的话要处理下
if (utils.isDate(v)) {
v = v.toISOString();
// 如果是个对象的话,那直接stringify就好
} else if (utils.isObject(v)) {
v = JSON.stringify(v);
}
// 数组里的样子就是这样的["a=1","v=2"]酱紫。
parts.push(encode(key) + "=" + encode(v));
});
});
// parts里面的参数都放完了,咱隔一下
serializedParams = parts.join("&");
// 此时的serializedParams就是这样的"a=1&v=2"了
} // 上面,我们根据不同的条件(自定义转换,URLSearchParams,默认)处理好了searchParams
// 下面,我们要处理hash
// 这个逻辑很简单,就是有hash的时候,只留下hash前的地址
if (serializedParams) {
console.log(url, "url"); var hashmarkIndex = url.indexOf("#");
// 要注意的是,如果hash存在,并且还存在参数,那么hash后面的参数也会被视为hash的一部分
if (hashmarkIndex !== -1) {
url = url.slice(0, hashmarkIndex);
}
console.log(url, "url");
// 判断下url有没有参数,根据不同条件分割searchParams
url += (url.indexOf("?") === -1 ? "?" : "&") + serializedParams;
} return url;
}

  首先啊,上面的代码都详细的写了注释。包括大家也可以去gitHub上看源码,好吧,跟axios一模一样,没有几乎,唉。。毕竟是抄的嘛。。。我在简单说下逻辑,首先,根据传入的参数判断要对params如何处理。如果既不存在自定义的转换方法又不是URLSearchParams对象,那么就会进入到我们自己的逻辑里。

  自己的逻辑里,用到了一个自定义的工具forEach方法,这个方法不多说,大家自己去源码的注释里看,循环的时候会判断下,这个key要是没有可使用的值就抛弃掉。那如果是数组,就转换一下key,如果不是,就把值变成一个数组,因为后面,我们要循环这个key的值,这块很重要,我们不仅要循环整个params对象,因为可能存在params中的值也是数据的情况,所以,还要循环遍历在params中的某个key的值是数组的情况。这样,如果值是数组的话,就会拼凑一个一个的key,刚好,之前我们把不是数组的也变成数组里,就可以单纯对数组进行循环处理。

  剩下的就比较简单,对Date和Object做一下特殊的处理,并且剔除hash。这里针对hash尤其要说一下,如果hash和searchParams同时存在,那么会连带一起抛弃掉的。比如www.baidu.com#query?a=1&b=2,那么处理后就只剩下www.baidu.com了。其他的就没了。

  我们在xhrAdapter方法中加上buildURL,然后,就可以查看结果了。

import buildURL from "../helpers/buildURL";
export default function xhrAdapter(config) {
var request = new XMLHttpRequest(); request.open(
config.method.toUpperCase(),
buildURL(config.url, config.params, config.paramsSerializer),
true
); request.send(config.data);
}

  该章节完整的代码在这里https://github.com/zakingWong/zaking-axios/tree/c1。哦对,额外要说的,该章节实现了几个工具方法,大家可以去lib/utils下查看,写了注释,这里就不多说了。不是主线任务,嘻嘻。

  我们也可以直接把项目clone到本地,npm run dev后,在对应的本地地址中查看效果,哦还有,run之前别忘了npm install一下。

参考:

  1. https://developer.mozilla.org/zh-CN/docs/Web/API/URLSearchParams/URLSearchParams
  2. https://zhuanlan.zhihu.com/p/29581070

一比一还原axios源码(一)—— 发起第一个请求的更多相关文章

  1. 一比一还原axios源码(零)—— 概要

    从vue2版本开始,vue-resource就不再被vue所维护和支持,官方也推荐使用axios,所以,从我使用axios至今,差不多有四五年了,这四五年的时间只能算是熟练应用,很多内部的实现和原理不 ...

  2. 一比一还原axios源码(四)—— Axios类

    axios源码的分析,到目前为止,算上第0章已经四章了,但是实际上,还都没有进入axios真正的主线,我们来简单回顾下.最开始我们构建了get请求,写了重要的buildURL方法,然后我们处理请求体请 ...

  3. 一比一还原axios源码(三)—— 错误处理

    前面的章节我们已经可以正确的处理正确的请求,并且通过处理header.body,以及加入了promise,让我们的代码更像axios了.这一章我们一起来处理ajax请求中的错误. 一.错误处理 首先我 ...

  4. 一比一还原axios源码(六)—— 配置化

    上一章我们完成了拦截器的代码实现,这一章我们来看看配置化是如何实现的.首先,按照惯例我们来看看axios的文档是怎么说的: 首先我们可以可以通过axios上的defaults属性来配置api. 我们可 ...

  5. 一比一还原axios源码(二)—— 请求响应处理

    上一章,我们开发了一些简单的代码,这部分代码最最核心的一个方法就是buildURL,应对了把对象处理成query参数的方方面面.虽然我们现在可以发起简单的请求了,但是第一,我们无法接收到服务器的响应, ...

  6. 一比一还原axios源码(八)—— 其他功能

    到此,我们完成了axios的绝大部分的功能,接下来我们来补全一下其他的小功能. 一.withCredentials  这个参数可以可以表明是否是一个跨域的请求.那这个的使用场景是啥呢?就是我们在同域的 ...

  7. 一比一还原axios源码(五)—— 拦截器

    上一篇,我们扩展了Axios,构建了一个Axios类,然后通过这个Axios工厂类,创建真正的axios实例.那么今天,我们来实现下Axios的拦截器也就是interceptors.我们来简单看下Ax ...

  8. Axios源码深度剖析 - 替代$.ajax,成为xhr的新霸主

    前戏 在正式开始axios讲解前,让我们先想想,如何对现有的$.ajax进行简单的封装,就可以直接使用原声Promise了? let axios = function(config){ return ...

  9. Axios源码分析

    Axios是一个基于promise的HTTP库,可以用在浏览器和node.js中. 文档地址:https://github.com/axios/axios axios理解和使用 1.请求配置 { // ...

随机推荐

  1. htc 简单的移动效果

    转载请注明来源:https://www.cnblogs.com/hookjc/ 1.创建 HTC 文件的架构.一个标准的 HTC 文件含有一个 SCRIPT 块和一对可选的 COMPONENT 标记. ...

  2. element ui图片上传方法

    <!--商品图片--> <template v-slot:product_cover> <el-upload list-type="picture-card&q ...

  3. ANT中的copy和move标签

    Copy标签 该标签用于文件或文件集的拷贝,其属性如下: file 表示源文件. tofile 表示目标文件. todir 表示目标目录. overwrite 表示指定是否覆盖目标文件,默认值是不覆盖 ...

  4. docker错误处理——docker Job for docker.service failed because the control process exited with error code.

    (15条消息) docker Job for docker.service failed because the control process exited with error code._Hel ...

  5. Lesson12——NumPy 字符串函数之 Part1:字符串操作函数

    NumPy 教程目录 1 NumPy 字符串函数 以下函数用于对 dtype 为 numpy.string_ 或 numpy.unicode_ 的数组执行向量化字符串操作. 它们基于 Python 内 ...

  6. 12、Linux基础--挂载磁盘步骤、流处理工具awk(正则 比较 逻辑 算数表达式 流程控制)

    笔记 1.晨考 1.用两种方法,实现将文件中的以# 开头的行把# 去掉 sed -r 's/^#//g' /etc/fstab cat /etc/fstab | tr -d '^#' 2.将文件中的H ...

  7. Pytorch技法:继承Subset类完成自定义数据拆分

    我们在<torch.utils.data.DataLoader与迭代器转换>中介绍了如何使用Pytorch内置的数据集进行论文实现,如torchvision.datasets.下面是加载内 ...

  8. 个人觉得好用的Idea插件

    Intellij IDEA插件 排名不分先后 1. Codota 代码智能提示插件 只要打出首字母就能联想出一整条语句,这也太智能了,还显示了每条语句使用频率.原因是它学习了我的项目代码,总结出了我的 ...

  9. kube-proxy的三种工作模式

    kube-proxy模式详解 kubernetes里kube-proxy支持三种模式,在v1.8之前我们使用的是iptables 以及 userspace两种模式,在kubernetes 1.8之后引 ...

  10. React 也就这样 01——React 元素的创建和渲染

    React 是一个用于构建用户界面的 JavaScript 库 它包括两个库:react.js 和 react-dom.js react.js:React 的核心库,提供了 React.js 的核心功 ...