- 算是头一次参加若川的活动,唯一的期望就是打开自己学习源码的思路、以及打开自己的视野,后续进行不断的优化和学习
- 真正的热爱学习,多参与、多查阅文档、多思考、形成自己的思维思想
- 如果或许对于你有一点点启发,那么也算是另外的一份收获
- 最后希望自己能坚持下去
- 本文阅读思路提示
- 1、本期要学习源码仓库地址
- 2、在线查看源码或者本地克隆代码
- 3、查看package.json文件
- 4、从入口文件的入口函数开始
- 5、查看typescript声明文件
- 6、查看测试用例文件
- 7、总结
1、本期要学习的源码仓库地址
- github仓库地址 arrify
- github1s: github1s.com/sindresorhu…
2、在线查看源码或者本地克隆代码
- 1、在线查看源码
- mac: 在github代码仓库页面通过快捷键 command + k,再在文本框中输入 ">",即可查看到在线打开的命令行
- window:同mac,只是快捷键改为ctrl + k
- 通用方法: 【<>Code】 按钮点击即可查看到Codespaces对应的Tab分组
- 2、本地克隆代码
- 直接在本地文件夹,克隆代码
git clone git@github.com:sindresorhus/arrify.git
3、查看package.json文件
- dependencies和devDependencies依赖查看
"ava": "^3.15.0", "tsd": "^0.14.0", "xo": "^0.39.1"
- 突然发现,这三个依赖都没见过,这里推荐一个小工具npm.devtool.tech, 这是山月大佬的自制小工具,感觉非常棒,安利一波。跳转网页后,直接输入npm库名即可查看,包的相关信息。
- ava: 19.8k star,通过查看readme.md可以发现,原来是一个单元测试的类库,还有中文翻译版本,具体翻译课查看 github.com/avajs/ava-d… ,它最大的优势应该就是可以并行的执行测试用例,这对于IO繁重的测试就特别的友好了。
- tsd: 1.4k star,也是star比较高的库,看似其貌不扬,但对于我个人来说,貌似又发现了新用法(以前从来没见过,涨姿势了)。检查typescript类型声明定义,让你通过xxx.test-d.ts后缀名,去为.xxx.d.ts类型声明去做测试(当然前提是你要是typescript)。
- xo: 6.6k star,xo基于ESLint。这个项目最初只是一个可共享的ESLint配置,但它很快就发展了.我想要更简单的东西.只是输入
xo
就完成.没有争论.没有配置.我也有一些令人兴奋的未来计划.但是,在使用ESLint时,您仍然可以获得大部分ESLint可共享配置,来自xo优势.
- scripts 脚本
"test": "xo && ava && tsd" // &&相当于串行(上一个执行完毕后才会继续执行下一个)、&相当于并行
- xo: 对代码进行检查 - ava: 执行根目录的test.js文件中的测试用例 - tsd: 通过index.test-d.ts文件测试index.d.ts定义
4、从入口文件的入口函数开始
main
: 定义了npm
包的入口文件,browser 环境和 node 环境均可使用
module
: 定义npm
包的 ESM 规范的入口文件,browser 环境和 node 环境均可使用
exports
:exports
的优先级比main
和module
高,也就是说,使用方只要匹配上exports
的路径就不会使用main
和module
的路径。
终于找到入口文件和入口函数index.js
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]; }
- 1、判断 null 或者undefined,则返回空的数组
- 2、判断传入的是一个数组的话,则返回它本身
- 3、判断传入的类型为string字符串的话,则返回一个数组长度为1的数组,第一个元素就为传入的字符串值
- 4、应该是本文要讲解的重点
对于两个单词分别的理解可以查看ES6文档,阮一峰老大讲解的应该非常清楚了,这里我就不进行意义赘述
- 关于Symbol: es6.ruanyifeng.com/#docs/symbo…
- 关于iterator: es6.ruanyifeng.com/#docs/itera…
- 关于Symbol.iterator es6.ruanyifeng.com/#docs/symbo…
那么我在这里要强调一下什么呢:
- ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值。(引用自ES6)
- 原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
通过上面的描述你或许还没能理解,我们通过代码简单的来学习一下
let iteratorArray = new Set() iteratorArray.add(1) iteratorArray.add('2') iteratorArray.add(['11', 22]) console.log(iteratorArray) /// Set(3) {1, '2', Array(2)} console.log(typeof iteratorArray ) /// object
我们对下面的obj对象封装一个简单的遍历器
let obj = { a: 'aa', b: 11, c: 'hellowrold' } obj = { ...obj, *[Symbol.iterator]() { console.log(this, 'this-item') yield { a: 'aa' } yield { b: 11 } yield { c: 'hellowrold' } } } for (const item of obj) { console.log(item, 'item') } // {a: 'aa'} 'item' // {b: 11} 'item' // {c: 'hellowrold'} 'item' // 那么obj 如果调用 arrify,对于其他的Object不生效 // 可以统一在Object.prototype[Symbol.iterator] 上实现 console.log(arrify(obj)) //(3) [{…}, {…}, {…}]0: {a: 'aa'}1: {b: 11}2: {c: 'hellowrold'}length: 3[[Prototype]]: Array(0)
再回到源码
if (typeof value[Symbol.iterator] === 'function') { return [...value]; }
现在再来看这个判断条件,其实就是判断value本身是否具有 Symbol.iterator属性,并且类型为function;也可以说成value是否是可遍历的,是否拥有接口 Iterator;还可以说成是否属于哪几种数据类型: Array、Map、Set、String、TypedArray、函数的arguments对象、NodeList对象,其中Array和String已经单独判断就可以不考虑了。
- 5、其他类型默认返回跟上述第三条一致
5、查看typescript声明文件
typescript类型声明,源代码连接 github.com/sindresorhu…
export default function arrify<ValueType>( value: ValueType ): ValueType extends (null | undefined) ? [] // eslint-disable-line @typescript-eslint/ban-types : ValueType extends string ? [string] : ValueType extends readonly unknown[] ? ValueType : ValueType extends Iterable<infer T> ? T[] : [ValueType];
- 条件类型判断: 关于这个类型声明中主要的一个关键字为extends,然后使用了(?:三目运算)。这里extends主要是作为条件判断的一个用法: ValueType extends (null | undefined) 前者ValueType 能够赋值给后面的类型,则返回 true,否则返回false。
- 继承: extends在 class中普通的用法就是继承
class Animal { name: string, constructor(name: string) { this.name = name } do(): void { console.log('动作') } } class Dog extends Animal { constructor(name:string){ super(name) } do(): void { console.log('汪汪汪') } } class Cat extends Animal { constructor(name:string){ super(name) } do(): void { console.log('喵喵喵') } }
6、查看测试用例文件
import test from 'ava'; import arrify from './index.js'; test('main', t => { t.deepEqual(arrify('foo'), ['foo']); // 判断字符串 t.deepEqual(arrify(new Map([[1, 2], ['a', 'b']])), [[1, 2], ['a', 'b']]); //Map对象 t.deepEqual(arrify(new Set([1, 2])), [1, 2]); //Set对象 t.deepEqual(arrify(null), []); // null t.deepEqual(arrify(undefined), []); // undefined const fooArray = ['foo']; t.is(arrify(fooArray), fooArray); //正常的数组 });
测试用例本身并没有什么花期呼哨的边界值
7、总结
别看真正封装的代码只有不到20行,但从整个npm库来说,这个小项目包括了非常多的知识点,当然这其中还有很多我没有发现的,或者是我忽略掉的知识点。但从我的知识盲区来说已经学到了非常多的知识。