一比一还原axios源码(一)—— 发起第一个请求
上一篇文章,我们简单介绍了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一下。
参考:
一比一还原axios源码(一)—— 发起第一个请求的更多相关文章
- 一比一还原axios源码(零)—— 概要
		
从vue2版本开始,vue-resource就不再被vue所维护和支持,官方也推荐使用axios,所以,从我使用axios至今,差不多有四五年了,这四五年的时间只能算是熟练应用,很多内部的实现和原理不 ...
 - 一比一还原axios源码(四)—— Axios类
		
axios源码的分析,到目前为止,算上第0章已经四章了,但是实际上,还都没有进入axios真正的主线,我们来简单回顾下.最开始我们构建了get请求,写了重要的buildURL方法,然后我们处理请求体请 ...
 - 一比一还原axios源码(三)—— 错误处理
		
前面的章节我们已经可以正确的处理正确的请求,并且通过处理header.body,以及加入了promise,让我们的代码更像axios了.这一章我们一起来处理ajax请求中的错误. 一.错误处理 首先我 ...
 - 一比一还原axios源码(六)—— 配置化
		
上一章我们完成了拦截器的代码实现,这一章我们来看看配置化是如何实现的.首先,按照惯例我们来看看axios的文档是怎么说的: 首先我们可以可以通过axios上的defaults属性来配置api. 我们可 ...
 - 一比一还原axios源码(二)—— 请求响应处理
		
上一章,我们开发了一些简单的代码,这部分代码最最核心的一个方法就是buildURL,应对了把对象处理成query参数的方方面面.虽然我们现在可以发起简单的请求了,但是第一,我们无法接收到服务器的响应, ...
 - 一比一还原axios源码(八)—— 其他功能
		
到此,我们完成了axios的绝大部分的功能,接下来我们来补全一下其他的小功能. 一.withCredentials 这个参数可以可以表明是否是一个跨域的请求.那这个的使用场景是啥呢?就是我们在同域的 ...
 - 一比一还原axios源码(五)—— 拦截器
		
上一篇,我们扩展了Axios,构建了一个Axios类,然后通过这个Axios工厂类,创建真正的axios实例.那么今天,我们来实现下Axios的拦截器也就是interceptors.我们来简单看下Ax ...
 - Axios源码深度剖析 - 替代$.ajax,成为xhr的新霸主
		
前戏 在正式开始axios讲解前,让我们先想想,如何对现有的$.ajax进行简单的封装,就可以直接使用原声Promise了? let axios = function(config){ return ...
 - Axios源码分析
		
Axios是一个基于promise的HTTP库,可以用在浏览器和node.js中. 文档地址:https://github.com/axios/axios axios理解和使用 1.请求配置 { // ...
 
随机推荐
- HTTPS的基本使用
			
1.https简单说明 HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的 ...
 - linux下core 相关设置
			
1)core文件简介core文件其实就是内存的映像,当程序崩溃时,存储内存的相应信息,主用用于对程序进行调试.当程序崩溃时便会产生core文件,其实准确的应该说是core dump 文件,默认生成位置 ...
 - 阿里云无法ping通解决
			
https://blog.csdn.net/longgeaisisi/article/details/78429099
 - SSH 远程访问及控制          ( 早上好,我是派大星,上帝派来爱你的那颗星)
			
远程访问及控制 1.SSH远程管理,TCP Wrappers访问控制 2.配置密钥对验证 1.SSH远程管理,TCP访问控制 SSH是一种安全通道协议,主要用来实现字符界面的远程登录.远程复制等功能: ...
 - java_JDBC,连接数据库方式,RestSet结果集,Statement,PreparedStatement,事务,批处理,数据库连接池(c3p0和Druid)、Apache-DBUtils、
			
一.JDBC的概述 1.JDBC为访问不同的数据薛是供了统一的接口,为使用者屏蔽了细节问题.2. Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作 ...
 - 了解Java格式化输出printf,一篇就够了
			
格式化详解 格式化输出 转换符 常用转换符 日期转换 搭配标志 了解C语言的都知道,C语言的输出语句printf();可以对里面的内容格式化然后输出.那么在Java中也给我们提供了相关的方法.两者十分 ...
 - 2、网络并发编程--套接字编程、黏包问题、struct模块、制作简易报头、上传文件数据
			
昨日内容回顾 面向对象复习(json序列化类) 对象.类.父类的概念 三大特性:封装 继承 多态 双下开头的方法(达到某个条件自动触发) __init__:对象实例化自动触发 __str__:对象执行 ...
 - MXNet学习-第一个例子:训练MNIST数据集
			
一个门外汉写的MXNET跑MNIST的例子,三层全连接层最后验证率是97%左右,毕竟是第一个例子,主要就是用来理解MXNet怎么使用. #导入需要的模块 import numpy as np #num ...
 - Java基于ClassLoder/ InputStream 配合读取配置文件
			
阅读java开源框架源码或者自己开发系统时配置文件是一个不能忽略的,在阅读开源代码的过程中尝尝困惑配置文件是如何被读取到内存中的.配置文件本身只是为系统运行提供参数的支持,个人阅读源码时重点不大可能放 ...
 - VS2019如何设置程序以管理员权限启动
			
最重要的一点.本文解释的是C#项目如何以管理员权限启动. 一个很大的误导项 该图片是C++程序的项目配置属性.C#项目中并找不到.然而网上的很多教程没有说清楚.导致我找了这个菜单找了很久. C#项目的 ...