梅开二度的 axios 源码阅读,三千字详细分享功能函数,帮助扩展开发思维

news2024/11/24 12:32:18

前言

第一遍看 axios 源码,更多的是带着日常开发的习惯,时不时产生出点联想。

第二遍再看 axios 源码,目标明确,就是奔着函数来的。

当有了明确清晰的目标,阅读速度上来了,思绪也转的飞快

按图索骥,接下来,将和大家一起,找寻 axios 源码中的功能函数,扩展一下开发思路。

文章速读

阅读文章,可以有以下收获:

功能函数

一般基础的工具函数都会放到 utils.js 文件中。前一篇源码阅读中,介绍了关于类型判断的两种方法。

axios 中有许多模块,不同的模块下包含不同的功能。除了 utils.js 文件,这些模块下的文件也包含函数。我称之为功能函数。

功能函数,按照实际功能需要封装的函数,也许并不是常见功能需要的,但是了解之后,没准能帮助今后用到的时候,快速完成开发。

1.standardBrowserEnv 标准浏览器中对 cookie 的读写删操作

axios 拥有对 cookie 操作的能力,就像叶一一会讲故事一样理所当然。

看源码功能之前,我们先来复习关于 cookie 的知识点。

cookie 的格式

通过 document.cookie 获取 cookie 的值,是一串有特定格式的字符串。它的格式是

cookie1=value; cookie2=value; cookie3=value;

了解了 cookie 的格式,对于它的操作,也自然而然的可以接着看代码了。

cookie 的读写删

在标准浏览器中,对 cookie 的处理被封装在了一个立即执行函数里。

(function standardBrowserEnv() {return {
		/** * cookie 写入(创建)单个写入 * @param {string} name cookie 名 * @param {string} value cookie 值 * @param {*} expires 过期时间 * @param {*} path cookie所在的目录 * @param {*} domain cookie所在的域 * @param {*} secure 是否可以通过HTTP协议的URL设置布尔值 值为 true 时,表示创建的 cookie 只能用 HTTPS 协议发送 */ write: function write(name, value, expires, path, domain, secure) {	// 先将所有的数据存入数组const cookie = [];cookie.push(name + '=' + encodeURIComponent(value));

			// 如果过期时间有值且是数值型,则存入其转为根据格林威治时间 (GMT) 转成的字符串if (utils.isNumber(expires)) {cookie.push('expires=' + new Date(expires).toGMTString());}	// 当目录存在且为字符串,则存入cookieif (utils.isString(path)) {cookie.push('path=' + path);}

			// 当域存在且为字符串,则存入cookieif (utils.isString(domain)) {cookie.push('domain=' + domain);}

			// 是否可以通过HTTP协议的URL设置布尔值为 true 时,则存入cookieif (secure === true) {cookie.push('secure');}	// 将数组转为字符串,并通过 document.cookie 属性来创建 cookiedocument.cookie = cookie.join('; ');},

		/** * cookie 读取 单个读取 * @param {string} name cookie 名 */read: function read(name) {	// 正则匹配得到数组const match = document.cookie.match(new RegExp('(^|;\s*)(' + name + ')=([^;]*)'));	// match 匹配的返回值中第四个元素为需要的值return match ? decodeURIComponent(match[3]) : null;},	/** * cookie 删除 单个删除 * @param {string} name cookie 名 */remove: function remove(name) {
			// 将过期时间设置为过去的时间,即可删除 cookie, 这里设置成了前一天(86400000为1天的毫秒数)this.write(name, '', Date.now() - 86400000);},};
})(); 

使用

// 写入
cookies.write('foo', 'bar');
// 删除
cookies.remove('foo');
// 读取
cookies.read('username=John Doe') // => John Doe 

功能汇总

日期转换 toGMTString 方法

toGMTString() 方法可根据格林尼治标准时间 (GMT) 把 Date 对象转换为字符串,并返回结果。

介绍这个方法,主要科普一个新旧交替的世界标准时间。

首先这个 GMT 和 UTC 的定义如下:

GMT(Greenwich Mean Time):格林尼治标准时间。格林尼治是英国伦敦南郊原皇家格林尼治天文台所在地,地球本初子午线的标界处,世界计算时间和经度的起点。格林尼治标准时间过去被当成世界标准的时间。

UTC(Coordinated Universal Time):协调世界时。又称世界统一时间、世界标准时间、国际协调时间。协调世界时是以原子时秒长为基础,在时刻上尽量接近于世界时的一种时间计量系统。协调世界时是现在使用的世界标准时间。

所以在W3C下面有一行提示:

不赞成使用此方法。请使用 toUTCString() 取而代之!!

toUTCString 方法

根据协调世界时 (UTC) 把 Date 对象转换为字符串,并返回结果。

正则 match 方法

match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。

返回值

它的返回值是一个存放匹配结果的数组。该数组的内容依赖于 regexp 是否具有全局标志 g。

  • 如果 regexp 没有标志 g,那么 match() 方法就只能在 stringObject 中执行一次匹配。

    • 如果找到匹配结果,它将返回一个数组,存放了与它找到的匹配文本有关的信息。数组的第1个元素存放的是匹配的文本,其余的元素存放与正则表达式的子表达式匹配的文本和input、index、groups三个元素。* 如果没找到匹配结果,返回 null。
  • 如果 regexp 具有标志 g,则 match() 方法将执行全局检索,找到 stringObject 中的所有匹配子字符串。

    • 如果找到匹配结果,它将返回一个数组,数组的第1个元素存放的是匹配的文本,其余元素存放匹配的一个或多个文本。* 如果没找到匹配结果,返回 null。

小结

1.对 cookie 的读写删操作还是挺简单的,主要是参数要考虑全面。日常开发中使用频率不是很高,但是如果需求涉及到可以帮助快速完成开发。
2.在 standardBrowserEnv 函数中,不难在里面发现 utils 的身影,所以基础工具函数的建设必不可少。
3.把 Date 对象转换为字符串推荐使用 toUTCString 方法。
4.无法删除的属性,可以设置成无效的值,比如这里的通过过期时间提前于当前时间。再比如前一篇介绍过将属性值设置为 undefined。

2.formDataToJSON 抽丝剥茧 formData 与 Object 的转换

FormData 对象

FormData 对象用以将数据编译成键值对,以便用XMLHttpRequest来发送数据。

FormData 对象主要用于发送表单数据,但亦可用于发送带键数据 (keyed data),而独立于表单使用。一般文件流数据的发送,会用到 FormData 对象。

第一条丝线— parsePropPath

matchAll

matchAll 函数会根据入参的正则表达式和字符串,返回所有匹配项的数组。

/**
 * It takes a regular expression and a string, and returns an array of all the matches
 *
 * @param {string} regExp - The regular expression to match against.
 * @param {string} str - The string to search.
 *
 * @returns {Array<boolean>}
 */
const matchAll = (regExp, str) => {let matches;const arr = [];

	// matches 为每次匹配正确的值while ((matches = regExp.exec(str)) !== null) {arr.push(matches);}return arr;
} 

regExp.exec 方法

在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null。

匹配的标准用法就是如上功能中的那样,如果需要进一步了解可以阅读 MDN 文档。

parsePropPath

parsePropPath ****函数会将字符串中的字母匹配出来放到数组中。

但看描述和函数还不太确定具体用法,但是经典的来了。

当我们设计的某个函数不好用文字做描述,可以用举例的方式辅助描述。比如下面这个函数,不但举例,还举了多个例子,所以这个函数的用途一目了然。

/**
 * It takes a string like `foo[x][y][z]` and returns an array like `['foo', 'x', 'y', 'z']
 *
 * @param {string} name - The name of the property to get.
 *
 * @returns An array of strings.
 */
function parsePropPath(name) {// foo[x][y][z]// foo.x.y.z// foo-x-y-z// foo x y zreturn utils.matchAll(/\w+|[(\w*)]/g, name).map(match => {	// 匹配值为数组,当匹配的第一个元素为空数组时返回空字符串,否则返回第二个元素或第一个元素存在值的那个。return match[0] === '[]' ? '' : match[1] || match[0];});
} 

第二条丝线— arrayToObject

arrayToObject 函数会将数组转换对象。

/**
 * Convert an array to an object.
 *
 * @param {Array<any>} arr - The array to convert to an object.
 *
 * @returns An object with the same keys and values as the array.
 */
function arrayToObject(arr) {const obj = {};
	// 返回数组索引值的数组const keys = Object.keys(arr);let i;const len = keys.length;let key;for (i = 0; i < len; i++) {key = keys[i];obj[key] = arr[key];}return obj;
} 

第三条丝线— forEachEntry

forEachEntry 函数会循环一个可迭代的对象,直到循环结束,把对象的 key 和 value 返回。

/**
 * For each entry in the object, call the function with the key and value.
 *
 * @param {Object<any, any>} obj - The object to iterate over.
 * @param {Function} fn - The function to call for each entry.
 *
 * @returns {void}
 */
const forEachEntry = (obj, fn) => {const generator = obj && obj[Symbol.iterator];const iterator = generator.call(obj);let result;// 循环直到迭代器已将序列迭代完毕while ((result = iterator.next()) && !result.done) {const pair = result.value;fn.call(obj, pair[0], pair[1]);}
}; 

我找了一个可迭代对象测试了一下输出

const data = new Map();

data.set('a', 1);
data.set('b', 2);
data.set('c', 3);

forEachEntry(data, (name, value) => {let res = name + '-' + value;console.log('name-value:', res);
});

// 输出结果
// > name-value: a-1
// > name-value: b-2
// > name-value: c-3 

formDataToJSON

formDataToJSON函数接受 一个 FormData 对象最终返回 JavaScript 对象。

/**
 * It takes a FormData object and returns a JavaScript object
 *
 * @param {string} formData The FormData object to convert to JSON.
 *
 * @returns {Object<string, any> | null} The converted object.
 */
function formDataToJSON(formData) {
	/** * 递归函数 将 FormData 的键值全部插入到给 target 对象 * @param {Array} path FormData 的属性名数组 * @param {string} value FormData 的属性值 * @param {Object} target 最终的目标对象 * @param {number} index FormData 的属性值数组的索引值 * @returns 递归是否中止的布尔值 */function buildPath(path, value, target, index) {let name = path[index++];
		// isFinite 函数会先将测试值转换为数字,然后再对其进行是否为有限数检测。const isNumericKey = Number.isFinite(+name);const isLast = index >= path.length;name = !name && utils.isArray(target) ? target.length : name;if (isLast) {
			// 如果检查对象具有该属性,将 value 添加到对应的数组中if (utils.hasOwnProp(target, name)) {target[name] = [target[name], value];} else {target[name] = value;}return !isNumericKey;}if (!target[name] || !utils.isObject(target[name])) {target[name] = [];}const result = buildPath(path, value, target[name], index);if (result && utils.isArray(target[name])) {target[name] = arrayToObject(target[name]);}return !isNumericKey;}

	// formData 参数是 FormData 类型且 formData.entries 是一个函数if (utils.isFormData(formData) && utils.isFunction(formData.entries)) {const obj = {};utils.forEachEntry(formData, (name, value) => {buildPath(parsePropPath(name), value, obj, 0);});return obj;}return null;
} 

我找 axios 自带的测试实例中的例子打印了一下结果

对象的值是数组

const formData = new FormData();
formData.append('foo', '1');
formData.append('foo', '2');

const res = formDataToJSON(formData);
console.log('res', res); // -> { foo: ['1', '2'] } 

对象

const formData = new FormData();

formData.append('foo', '1');
formData.append('bar', '2');

const res = formDataToJSON(formData);
console.log('res', res); // -> {foo: '1', bar: '2'} 

嵌套对象

const formData = new FormData();

formData.append('foo[bar][baz]', '123');

const res = formDataToJSON(formData);
console.log('res', res); // -> { foo : { bar : {baz: '123'} } } 

小结

1.formDataToJSON 函数接受 一个 FormData 对象最终返回 JavaScript 对象。
2.FormData() 构造函数用于创建一个新的FormData对象。该函数在 Web 中可用,一些环境会报错“FormData is not define”。
3.FormData 对象转换 JavaScript 对象的功能不是很常用。但是这里的主要收获是,当函数设计的相对复杂的时候,可以用抽丝剥茧的方式,现将支线整理出来,最终拼凑成一个完整的主线。

总结

功能函数会依据实际需求去实现功能。通过分析 axios 源码中的两个重点的功能函数,学习复杂功能如何设计,以及补充了些知识点,还得到目前代码中有些判断条件简化的收获。

很愉快的一次源码阅读体验。

axios 源码阅读历程

捎带分享一下我关于 axios 源码阅读中的历程。

数月前

因为工作中需要弄清楚 axios 的 request 中的入参而在源码中寻找答案,当时想抽时间进行一次源码阅读,但是没有坚持下去,就此作罢。

数日前

前几天做功能联想,重新开始阅读,这次收获颇多。

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/333788.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

day 15 第六章二叉树

层序遍历 102.二叉树的层序遍历107.二叉树的层次遍历II199.二叉树的右视图637.二叉树的层平均值429.N叉树的层序遍历515.在每个树行中找最大值116.填充每个节点的下一个右侧节点指针117.填充每个节点的下一个右侧节点指针II104.二叉树的最大深度111.二叉树的最小深度 226.翻转二…

速腾rshelios 5515惯导时间同步

目前接触过两种雷达和惯导同步的方式&#xff1a; 1.惯导输出gprms和pps信号给米文系统&#xff0c;雷达驱动从系统里读取时间。 2.惯导输出gprms和pps信号给雷达&#xff0c;雷达驱动从雷达数据读取时间。 GPRMS和PPS的内容参考&#xff1a;STM32模拟GPS输出PPS、GPRMC与VLP…

多传感器融合定位九-基于滤波的融合方法Ⅰ其一

多传感器融合定位九-基于滤波的融合方法Ⅰ其一1. 滤波器的作用2. 概率基础知识2.1 概率、概率密度2.2 联合概率密度2.3 条件概率密度2.4 贝叶斯公式2.5 贝叶斯推断2.6 高斯概率密度函数2.7 联合高斯概率密度函数2.8 高斯随机变量的线性分布1. 滤波器的作用 滤波器的本质&#…

编译与链接------《程序员的自我修养》

本篇整理于《程序员的自我修养》一书中编译与链接相关知识&#xff0c;整理的目的是为了更加深入的了解编译于链接的更多底层知识&#xff0c;面对程序运行时种种性能瓶颈我们束手无策。我们看到的是这些问题的现象,但是却很难看清本质&#xff0c;所有这些问题的本质就是软件运…

Allegro如何使用Vertext命令修改丝印线段的形状操作指导

Allegro如何使用Vertext命令修改丝印线段的形状操作指导 在用Allegro画丝印线段的时候,如果画了一段不是自己需要形状的线段,无需删除重画,可以用Vertext命令直接编辑 如下图 修改前 修改后 具体操作如下 选择Edit

Java笔记-JUC基础

1、什么是JUC JUC指的是java.util三个并发编程工具包 java.util.concurrentjava.util.concurrent.atomicjava.util.concurrent.locks 2、线程的几种状态 public enum State{NEW,//新建RUNNABLE,//准备就绪&#xff0c;等待资源BLOCKED,//阻塞WAITING,//一直等待TIMED_WAITI…

vue脚手架 element-ui spring boot 实现图片上传阿里云 并保存到数据库

一.阿里云 注册登陆就不讲了&#xff0c;登陆进去后如下操作 1. 进入对象存储OSS 创建一个新的Bucket 随后点击新建的bucket 2.去访问RAM 前往RAM控制台 3.去创建用户 4.创建密匙 5.随后返回RAM控制台 给用户增加权限&#xff0c;文件上传所需权限&#xff0c;需要带含有…

LeetCode——1797. 设计一个验证系统

一、题目 你需要设计一个包含验证码的验证系统。每一次验证中&#xff0c;用户会收到一个新的验证码&#xff0c;这个验证码在 currentTime 时刻之后 timeToLive 秒过期。如果验证码被更新了&#xff0c;那么它会在 currentTime &#xff08;可能与之前的 currentTime 不同&am…

Java IO模型详解

文章目录Java IO模型详解一、I/O的定义1、计算机结构的视角2、应用程序的视角二、Java 中3种常见的 I/O 模型1、同步阻塞 I/O&#xff08;BIO&#xff09;2、同步非阻塞 I/O&#xff08;NIO&#xff09;★ I/O 多路复用模型3、异步非阻塞 I/O&#xff08;AIO&#xff09;Java I…

Flutter 小技巧之 3.7 更灵活的编译变量支持

今天我们聊个简单的知识点&#xff0c;在 Flutter 3.7 的 release-notes 里&#xff0c;有一个没有出现在 announcement 说明上的 change log &#xff0c;可能对于 Flutter 团队来说这个功能并不是特别重要&#xff0c;但是对于我个人而言&#xff0c;这是一个十分重要的能力补…

什么是模板方法模式?

在面向对象程序设计过程中&#xff0c;程序员常常会遇到这种情况&#xff1a;设计一个系统时知道了算法所需的关键步骤&#xff0c;而且确定了这些步骤的执行顺序&#xff0c;但某些步骤的具体实现还未知&#xff0c;或者说某些步骤的实现与具体的环境相关。例如&#xff0c;去…

阿里云国际服务器ECS特性与优势

阿里云国际服务器的 ECS 作为一种安全、可靠、灵活、可扩展的云计算服务&#xff0c;不仅可以减少运行和维护&#xff0c;而且可以提高运行和维护效率&#xff0c;使用户关注核心业务的创新。 阿里云国际服务器ECS优势一&#xff1a;产品丰富 阿里云国际云服务器 ECS 可以提供…

《MySql学习》 SQL 语句的更新过程

《MySql学习》 SQL 语句的更新过程 一.SQL查询语句的执行过程 上一篇博文记录了SQL查询语句的执行过程&#xff0c;首先客户端通过TCP三次握手与Server层的连接器建立连接&#xff08;短连接与长链接&#xff09;&#xff0c;缓存权限。然后去查询缓存&#xff08;8.0后移除&…

联想服务器双品牌的思考:融合化、场景化、订阅化、绿色化,打造全栈新算力基础设施

联想集团执行副总裁兼中国区总裁刘军&#xff1a;智能化转型是中国企业未来十年穿越经济周期的利器&#xff0c;智能化生产力水平决定了企业发展的速度与高度。 联想创新性提出融合化、场景化、订阅化及绿色化的“四维算力”&#xff0c;致力于成为中国领先的智能IT基础设施提供…

微服务--Feign学习

Feign远程调用&#xff1a; RestTemplate发起远程调用的代码&#xff1a; 存在下面的问题 代码可读性差&#xff0c;编程体验不统一参数复杂URL难以维护 Feign的介绍&#xff1a;Feign是一个声明式的http客户端&#xff0c;官方地址&#xff1a;https://github.com/OpenFeign/…

论文文献引用规范和标准(国标GBT7714)@endnote国标样式

文章目录论文文献引用规范和标准&#xff08;国标GBT7714&#xff09;国标GBT7714-2015endnote stylerefs简述国标GBT7714条目的组织格式Noteword中的文献交叉引用超链接文献引用示例endNote资源和基本使用endnote或其他文献引用工具下载word中的其他引文技巧知网国标格式引文引…

谈谈Java Optional的坑

开端&#xff1a; 大家好&#xff0c;我是老白。昨天朋友提出的java8后出来的自带的对象判定方式Optional.ofNullable(),后来查询了一些资料和自己试验了一些demo资料&#xff0c;在这里记录分享个大家 作用&#xff1a;判断对象是否为空&#xff0c;是则重新创建一个新对象&…

ABAP 搜索帮助带出多个字段描述 更新屏幕字段

文章目录需求解析1-DYNP_GET_STEPL2-F4IF_INT_TABLE_VALUE_REQUEST3-获取返回值4-把相应字段更新到内表5-DYNP_VALUES_UPDATE代码需求 如图,当我点击责任工序的搜说帮助时, 同时会把责任人员的描述带出来. 解析 1-DYNP_GET_STEPL 这个方法就是获取当前的循环步骤 2-F4IF_I…

浏览器中的 JavaScript 执行机制

思维导图 本文为反复学习极客时间-《浏览器的工作原理与实践》-浏览器中的 JavaScript 执行机制章节中的一些思考与记录。 一些重要概念 变量提升 所谓的变量提升&#xff0c;是指在 JavaScript 代码执行过程中&#xff0c;JavaScript 引擎把变量的声明部分和函数的声明部分…

你可能还不知道的 console.log 替代品

通过使用 javascript 对象的破坏能力&#xff0c;您可以这样做&#xff1a;const{ log } console; log("hi"); log("testing");你可以将log函数更改为您想要的任何其他名称&#xff0c;如下所示&#xff1a;const{log: myLog } console; myLog("hi&qu…