【若川视野 x源码共读】第33期 | arrify 转数组 Convert a value to an array

853 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。


  • 算是头一次参加若川的活动,唯一的期望就是打开自己学习源码的思路、以及打开自己的视野,后续进行不断的优化和学习

  • 真正的热爱学习,多参与、多查阅文档、多思考、形成自己的思维思想

  • 如果或许对于你有一点点启发,那么也算是另外的一份收获

  • 最后希望自己能坚持下去

  • 本文阅读思路提示

    • 1、本期要学习源码仓库地址
    • 2、在线查看源码或者本地克隆代码
    • 3、查看package.json文件
    • 4、从入口文件的入口函数开始
    • 5、查看typescript声明文件
    • 6、查看测试用例文件
    • 7、总结

1、本期要学习的源码仓库地址

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文档,阮一峰老大讲解的应该非常清楚了,这里我就不进行意义赘述

    | 那么我在这里要强调一下什么呢:

    • 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库来说,这个小项目包括了非常多的知识点,当然这其中还有很多我没有发现的,或者是我忽略掉的知识点。但从我的知识盲区来说已经学到了非常多的知识。