读了会 axios 源码,联想到了三个有趣的对比【玩转源码】
源码阅读
最近翻来了 axios 源码,信心满满的看了会,虽然哪跟哪都没串起来,但是意外收获了一些新的想法。
有几组不错的知识点,对比看,比单独看每个知识点,更有趣一些。
遇到有趣的知识点,当然要分享一下。
文章速读
本文从 axios 的源码联想到了几个不错的知识点对比。
阅读文章,可以有以下收获:
知识点对比开始
delete or undefined, 谁是更好的选择?
技术讨论小剧场
一:这里为什么要用 delete 删除这个属性。
某:因为异步请求里不能带上这个属性。
一:把值设置成 undefined,JSON.stringify 方法会过滤掉值为 undefined 的对象属性。
某:这个方法好像挺不错的。等等,我要是没有用到 JSON.stringify 方法呢?
一:写个判断方法。
某:键盘已递上。
undefined 值的判断方法
const typeOfTest = type => thing => typeof thing === type;
/**
* Determine if a value is undefined
*
* @param {*} val The value to test
*
* @returns {boolean} True if the value is undefined, otherwise false
*/
const isUndefined = typeOfTest('undefined');
const val = undefined;
const val2 = 2;
isUndefined(val) // true
isUndefined(val2) // false
某: 怎么写的这么快!
一:当然了,这可是 axios 源码里采用的方法。
某:所以结论就是,推荐将对象的值设置为 undefined,而不是delete 它。
一:是的。
某:再讲点 delete 的知识点。这个操作符,我还不太熟悉。
一:这个可以有。正好可以正向思维和逆向思维齐上阵。
delete 删除了什么?
删除对象的某个属性
我们会按照 MDN 文档定义的那样,「delete 操作符用于删除对象的某个属性」,先来尝试删除对象的属性:
var obj = {
name: 'ye',
age: 18,
};
delete obj.age;
console.log(delete obj.age); // true
console.log(obj); // { name: 'ye' }
非常符合预期的结果,delete 返回值是 true,且 obj 已不存在 age 属性。
如果删除一个对象不存在的属性,会怎样?
var obj = {
name: 'ye',
age: 18,
};
delete obj.desc;
console.log(delete obj.desc); // true
console.log(obj); // { name: 'ye', age: 18 }
这种情况也很好解释,MDN 官网已经给了文字解释:
如果你试图删除的属性不存在,那么 delete 将不会起任何作用,但仍会返回 true。
delete 'zhang' or delete 18 结果会怎样?
console.log(delete 'zhang'); // true
console.log(delete 18); // true
结果都是 true,有意思。其实,这里的字符串也好,数字也好,其实是单值表达式的结果,delete 删除的是这个表达式的结果,所以返回了 true。
所谓单值表达式是指没有运算符的表达式,一般值是其本身。
删除 let 或 const 声明的属性会怎样?
var name = 'ye';
or
var name = obj.name;
console.log(delete name); // false
结果是 false,因为任何用 let 或 const 声明的属性不能够从它被声明的作用域中删除。
小结
- 不需要某个对象属性时,推荐将对象的值设置为 undefined,而不是delete 它。
- 经过一系列实验会发现,delete 其实删除的是一个表达式的引用类型的结果。
- axios 源码内容虽然不是特别多,但是完整的梳理清楚还是挺耗时的。我帮大家画个入口示意图,感兴趣的可以下载到本地,慢慢赏析。☞axios github
yield 和 return,这俩有故事?
技术讨论小剧场
一:为何眉头紧锁。
某:正在看 axios 的源码。
一:我理解了。
某:我不理解,这里为什么要用 yield。
一: 这么快看到这里了。
某:我只是先打开了这个文件而已。
品品为什么要用 yield
axios 源码里面有一段代码,在测试用例里面,实际的代码无法直接打印,我稍作了调整,如下:
const count = 10;
const chunkLength = 10;
const contentLength = count * chunkLength;
const samples = Array.from(
(function* () {
for (let i = 1; i <= 10; i++) {
yield {
loaded: chunkLength * i,
total: contentLength,
progress: (chunkLength * i) / contentLength,
bytes: 4,
download: true,
};
}
})(),
);
打印 samples 的结果
某:如果这里的代码把 yield 改成 return 会怎样。
一:你猜。
不卖关子,其实会打印一个空数组。
console.log(samples); // []
某:能用 return 实现上面的截图中的数组吗?
一:当然可以。
某:键盘和膝盖双双奉上。
Array.from 支持通过伪数组对象创建数组对象。伪数组对象是指拥有一个 length 属性和若干索引属性的任意对象。
const count = 10;
const chunkLength = 10;
const contentLength = count * chunkLength;
const samples = Array.from({ length: count }, (_, j) => {
let i = j + 1;
return {
loaded: chunkLength * i,
total: contentLength,
progress: (chunkLength * i) / contentLength,
bytes: 4,
download: true,
};
});
yield 和 return 的故事
相同场景
这俩功能看着挺相似的,都能返回值给函数调用者。比如下面这两个代码块打印的结果是一样的。
使用 yield
function* foo(index) {
while (index < 2) {
index++;
yield index;
}
}
const iterator = foo(0);
console.log(iterator.next().value); // 1
使用 return
function* foo(index) {
while (index < 2) {
index++;
return index;
}
}
const iterator = foo(0);
console.log(iterator.next().value); // 1
如果把返回的这行代码提升会怎么样?
使用 yield
function* foo(index) {
while (index < 2) {
yield index;
index++;
}
}
const iterator = foo(0);
console.log(iterator.next().value); // 0
console.log(iterator.next().value); // 1
使用 return
function* foo(index) {
while (index < 2) {
return index;
index++;
}
}
const iterator = foo(0);
console.log(iterator.next().value); // 0
console.log(iterator.next().value); // undefined
结果不一样了。可见这俩有个明显的不同:
- return 语句终止函数的执行。
- yield 语句挂起当前函数,而下一次调用 next() 时,在 yield 之后紧接着的语句会继续执行。
听说 yield 能传值?
来看下面这个例子
function* func(x) {
console.log('x:', x);
x--;
x = yield x;
}
let generatorFunc = func(10);
let value = generatorFunc.next().value;
console.log('value:', value);
打印结果
x: 10 // 第一个console
value: 9 // 第二个console
解析一下,第一个 console,x 值来自函数的传参,这个很容易理解。第二个 console,这里 x 值来自调用 generatorFunc.next(arg) 时的传参 arg,而 arg 来自 yield 表达式的结果。
小结
- yield 关键字用于暂停和恢复生成器函数。
- yield 和 return 的故事在于,yield可以被认为是一个基于生成器的版本的 return 关键字。但是 yield 功能更丰富。
- axios 源码,看到哪里算哪里吧。
typeof and toString, 类型校验专场
技术讨论小剧场
某:还记得我们前面讨论的如何判断值为 undefined 吗?
一:当然,就在文章前面没多远的地方。
某:我又找到了一种判断方式。
一:替换方式?哪种更好?
某:我愿称之为 typeOfTest 的升级版。
toString:更辽阔的类型校验
先来看看 axios 源码是怎么实现对引用类型(其实包括基础类型)的校验。
const { toString } = Object.prototype;
const kindOf = (cache => thing => {
const str = toString.call(thing);
return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
})(Object.create(null));
const kindOfTest = type => {
type = type.toLowerCase();
return thing => kindOf(thing) === type;
};
/**
* Determine if a value is a Date
*
* @param {*} val The value to test
*
* @returns {boolean} True if value is a Date, otherwise false
*/
const isDate = kindOfTest('Date');
console.log(isDate(new Date())); // true
console.log(isDate('ye')); // false
上面的方法,核心在于使用 Object.prototype.toString.call() 获取数据类型,它返回 "[object Type]",使用正则匹配可以得到最终的 Type。
下面我们用 Object.prototype.toString.call() 打印一下绝大多数的数据类型,看结果否和我们预期的一样:
console.log(Object.prototype.toString.call(111)); // [object Number]
console.log(Object.prototype.toString.call('ye')); // [object String]
console.log(Object.prototype.toString.call(true)); // [object Boolean]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object ReUndefinedgExp]
console.log(Object.prototype.toString.call(Symbol())); // [object Symbol]
console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call(function () {})); // [object Function]
console.log(Object.prototype.toString.call(new Error())); // [object Error]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
console.log(Object.prototype.toString.call(new RegExp())); // [object RegExp]
console.log(Object.prototype.toString.call(Map)); // [object Function]
console.log(Object.prototype.toString.call(Math)); // [object Math]
可以得到几乎所有的数据类型。
小结
- Object.prototype.toString.call() 几乎可以实现所有数据类型的校验。
- 想要获取准确的数据类型,还要在 [object Type] 的基础上通过正则匹配获取最终的 Type。
- 方法都是现成的,需要哪个用哪个。掌声送给 axios 源码。
总结
整个 axios 源码相较而言,不算特别多,但是想要全部梳理明白,还是需要一定的时间和精力的。不过,从一些工具函数文件出发,很容易吸收部分功能设计,比如 util 文件。
本文从 axios 源码中的功能结合日常开发,总结了三个有趣的对比实验,希望能对今后的开发有帮助。
另外,若川大佬的《学习 axios 源码整体架构,打造属于自己的请求库》,对 axios 源码的整体架构做了详细介绍,推荐阅读。
- 点赞
- 收藏
- 关注作者
评论(0)