【若川视野 x 源码共读】 第33期 | arrify 转数组

125 阅读2分钟

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

1. 学习准备

  • github仓库地址 arrify
  • github1s: github1s.com/sindresorhu…
  • 这期源码行数不多, 就十几行。
  • 学习 Symbol.iterator 的使用场景
  • 这期比较简单,主要学会通过测试用例调试源码。 可以多关注怎么发布npm包的、esm、测试用例 、ts 等(也可以不关注)。
  • 建议克隆代码下来,关注测试用例,自己多通过测试用例调试,自己调试过才能够学会,感受更深一些

2. 学习过程

2.1 arrify 包是什么

Convert a value to an array ,即将一个值转化为数组形式。

2.2 如何安装和使用

npm install arrify
或者
yarn add arrify
import arrify from 'arrify';

arrify('🦄');
//=> ['🦄']

arrify(['🦄']);
//=> ['🦄']

arrify(new Set(['🦄']));
//=> ['🦄']

arrify(null);
//=> []

arrify(undefined);
//=> []

2.3 查看源码

  • 1、在线查看源码

    • mac: 在github代码仓库页面通过快捷键 command + k,再在文本框中输入 ">",即可查看到在线打开的命令行
    • window:同mac,只是快捷键改为ctrl + k
    • 通用方法: 【<>Code】 按钮点击即可查看到Codespaces对应的Tab分组
  • 2、本地克隆代码

    • 直接在本地文件夹,克隆代码
git clone git@github.com:sindresorhus/arrify.git

克隆到本地之后,查看package.json文件,发现依赖了3个包,分别是ava、tsd、xo。

image.png

  • ava:  简单便捷的测试运行器,有中文翻译版本,具体翻译课查看 github.com/avajs/ava-d… ,它最大的优势应该就是可以并行的执行测试用例,这对于IO繁重的测试就特别的友好了。
  • tsd:  支持为 TypeScript 类型定义文件编写测试,检查typescript类型声明定义,让你通过xxx.test-d.ts后缀名,去为.xxx.d.ts类型声明去做测试。
  • xo:  开箱即用的 Linter,缩减掉了 ESLint 的配置工序,,xo基于ESLint。这个项目最初只是一个可共享的ESLint配置,但它很快就发展了.我想要更简单的东西.只是输入xo就完成.没有争论.没有配置.我也有一些令人兴奋的未来计划.但是,在使用ESLint时,您仍然可以获得大部分ESLint可共享配置,来自xo优势。

image.png

2.4 源码代码

export default function arrify(value) {
    // 判断 null 或者undefined,则返回空的数组
    if (value === null || value === undefined) {
            return [];
    }
    // 判断传入的是一个数组的话,则返回它本身
    if (Array.isArray(value)) {
            return value;
    }
    // 判断传入的类型为string字符串的话,则返回一个数组长度为1的数组,第一个元素就为传入的字符串值
    if (typeof value === 'string') {
            return [value];
    }

    if (typeof value[Symbol.iterator] === 'function') {
            return [...value];
    }

    return [value];
}

对于Symbol和iterator的理解可以查看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' };
    }, 
 };
console.log(arrify(obj)); // [{a: 'aa}, {b: 11}, {c: 'helloworld'}]

image.png

| 再回头看源码

if (typeof value[Symbol.iterator] === 'function') {
    return [...value];
}

现在再来看这个判断条件,其实就是判断value本身是否具有 Symbol.iterator属性,并且类型为function;也可以说成value是否是可遍历的,是否拥有接口 Iterator;还可以说成是否属于哪几种数据类型: Array、Map、Set、String、TypedArray、函数的arguments对象、NodeList对象,其中ArrayString已经单独判断就可以不考虑了。

2.5 测试用例源码

import {expectType, expectError, expectAssignable} from 'tsd';
import arrify from './index.js';

expectType<[]>(arrify(null));
expectType<[]>(arrify(undefined));
expectType<[string]>(arrify('🦄'));
expectType<string[]>(arrify(['🦄']));
expectAssignable<[boolean]>(arrify(true));
expectType<[number]>(arrify(1));
expectAssignable<[Record<string, unknown>]>(arrify({}));
expectType<[number, string]>(arrify([1, 'foo']));
expectType<Array<string | boolean>>(
	arrify(new Set<string | boolean>(['🦄', true]))
);
expectType<number[]>(arrify(new Set([1, 2])));
expectError(arrify(['🦄'] as const).push(''));
expectType<[number, number] | []>(arrify(false ? [1, 2] : null));
expectType<[number, number] | []>(arrify(false ? [1, 2] : undefined));
expectType<[number, number] | [string]>(arrify(false ? [1, 2] : '🦄'));
expectType<[number, number] | [string]>(arrify(false ? [1, 2] : ['🦄']));
expectAssignable<number[] | [boolean]>(arrify(false ? [1, 2] : true));
expectAssignable<number[] | [number]>(arrify(false ? [1, 2] : 3));
expectAssignable<number[] | [Record<string, unknown>]>(arrify(false ? [1, 2] : {}));
expectAssignable<number[] | [number, string]>(
	arrify(false ? [1, 2] : [1, 'foo'])
);
expectAssignable<number[] | Array<string | boolean>>(
	arrify(false ? [1, 2] : new Set<string | boolean>(['🦄', true]))
);
expectAssignable<number[] | [boolean] | [string]>(
	arrify(false ? [1, 2] : (false ? true : '🦄'))
);

我们根据测试用例来写一些例子:

    console.log(arrify(null)); // []
    console.log(arrify(undefined)); // []
    console.log(arrify('string')); // ['string']
    console.log(arrify(['first'])); // ['first']
    console.log(arrify(true)); // [true]
    console.log(arrify(1)); // [1]
    console.log(arrify({})); // [{}]
    console.log(arrify([1, 'foo'])); // [1, 'foo']
    console.log(arrify(new Set([1, 2]))); // [1, 2]
    console.log(arrify(new Set([1, 'foo', true]))); // [1, 'foo', true]
    console.log(arrify(false ? [1, 2] : null)); // []
    console.log(arrify(false ? [1, 2] : undefined)); // []
    console.log(arrify(false ? [1, 2] : 'string')); // ['string']
    console.log(arrify(false ? [1, 2] : ['111'])); // ['111']
    console.log(arrify(false ? [1, 2] : true)); // [true]
    console.log(arrify(false ? [1, 2] : 3)); // [3]
    console.log(arrify(false ? [1, 2] : {})); // [{}]
    console.log(arrify(false ? [1, 2] : [1, 'foo'])); // [1, 'foo']
    console.log(arrify(false ? [1, 2] : new Set(['111', true]))); // ['111', true]
    console.log(arrify(false ? [1, 2] : (false ? true : '222'))); // ['222']

3. 学习收获或感受

  1. 看似历史上最简单的一期源码阅读,结果是越看越不简单呐,越看,牵扯的知识点越多,很多自己还不是理解得很深。
  2. 在github代码仓库页面通过快捷键 command + k,再在文本框中输入 ">",即可查看到在线打开的命令行,确实很方便查找代码,也可以修改代码,但是我的不支持在线打开终端调试源码唉。。。
  3. 不得不说,别人的笔记真的好优秀哦。
  4. 大家在平常的业务开发中,使用Symbol了吗?一般是什么样的场景建议使用呢?有没有遇到什么坑啊?

4. 参考文章

  1. juejin.cn/post/710594…

  2. juejin.cn/post/710463…