前言
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
本篇是源码共读第33期 | arrify转数组,点击了解本期详情
准备
首先arrify是什么?官方说明是将传入的值转换为数组,同时也给出了几个样例
import arrify from 'arrify';
arrify('🦄');
//=> ['🦄']
arrify(['🦄']);
//=> ['🦄']
arrify(new Set(['🦄']));
//=> ['🦄']
arrify(null);
//=> []
arrify(undefined);
//=> []
- 拉取代码
git clone https://github.com/sindresorhus/arrify.git
- 安装依赖
npm i
- 查看package.json依赖
"scripts": {
"test": "xo && ava && tsd"
},
"devDependencies": {
"ava": "^3.15.0",
"tsd": "^0.14.0",
"xo": "^0.39.1"
}
- ava,基于Node.js 的单元测试包,支持Typescript
- tsd,通过.test-d.ts 文件做类型定义检查,它们不会被编译测试,只会对类型定义进行静态分析
- xo,集成了linter规则的ESLint包,不需要再做额外配置
那么执行npm run test 后做了什么呢?
-
xo对 js 和 ts 文件做 规则检查 ava跑 test.js 测试 index.jstsd跑 index.test-d.ts 测试 index.d.ts
看源码前的思考
如果让我完成这个需求,我会考虑哪些点,会从哪块入手,如何编写此功能?
首先肯定会根据数据类型来,那数据类型有哪些呢?基本数据类型(Number,String,Boolean,Undefined,Null,还有之后新增的BigInt以及Symbol),引用数据类型(Object,Array,Function,以及RegExp和Date)。
先检查是否为null和undefined,如果是直接返回个空数组,其次是如果传入的就是个数组那就直接原封不对返回即可,其他的用数组包裹返回出去,重点就在其他的如何界定,如何去判断呢?我先想到的是判断类型,用typeof判断类型是哪种然后再返回对应的,但是这样判断太多了,就被我pass掉了然后又没想到新方案,带着这个疑问来查看了源码(其实是也只能想到这么多了😂)
源码
由于我实在搞不懂单元测试啥的,这知识怎么都进不了脑子,所以干脆专注源码好了,单元测试什么的等知识进脑子了我再回来补充吧
export default function arrify(value) {
if (value === null || value === undefined) {
return [];
}
if (Array.isArray(value)) {
return value;
}
if (typeof value === 'string') {
return [value];
}
if (typeof value[Symbol.iterator] === 'function') {
return [...value];
}
return [value];
}
嗯,首先判断了null和undefined和我想的差不多,接下来是数组,哎又对上了看了思路是对的。接下来是string,难道要一个个判断吗?嗯?Symbol.iterator?function?这是什么?
Symbol.iterator
说实话要不然看这个源码,我都忘记迭代器是什么了😂唉忘光了。简单来说它为每一个对象定义了默认的迭代器,该迭代器可以被 for...of 循环使用。像String,Array,Map,Set这些就是默认就具有这种迭代器的,换句话说,这个对象需要有一个属性,并且属性名为Symbol.iterator,下面编写下代码具体看下
let arr = new Array();
arr = [1, 2, 3, 4];
console.log(arr);
打印下这个属性
console.log(arr[Symbol.iterator]);
// => ƒ values() { [native code] }
看到返回一个函数,那我们再调用下这个函数
console.log(arr[Symbol.iterator]());
哎里面还有个next方法,那我们再调用一下吧
console.log(arr[Symbol.iterator]().next());
// => { value: 1, done: false }
可以看到返回了一个含有value 和done属性的对象
- value ,迭代器返回的当前值,done为true则为undefined
- done,是不是已经到了结尾,是一个布尔值,如果为true则意味着迭代结束
生成迭代器函数
function iteratorFn(objIterator) {
let index = 0;
return {
next: function () {
if (index < objIterator.length) {
return { done: false, value: objIterator[index++] };
} else {
return { done: true, value: undefined };
}
},
};
}
到这倒是明白了typeof value[Symbol.iterator] === 'function'但是不明白为什么要把string类型单独拿出去写,然后在共读群里问了下,若川大佬解答说是字符串扩展时候就不对了。嗯?还有我没看到的地方?好,继续看。哎,不用看了,是因为判断体里面写的是return [...value]服了自己了
for...of关键字和 扩展运算符...,它的本质就是迭代器的语法糖,只有具有可迭代的特性才能使用。好了现在总结下源码逻辑
- Null 和 Undefined 返回空数组
- 如果是数组不必再转换,返回本身
- 判断String 让其保持整体作为数组元素存在
- 可迭代对象,浅拷贝迭代值创建新数组
- 其他类型直接被数组包裹返回
总结
- 学习
arrify源码 - 复习
Symbol.iterator
不足之处:
- 个人平时编写代码时没有针对写过测试用例,所以整体流程还是有些懵,只会看源码