本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
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。
- 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优势。
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文档,阮一峰老大讲解的应该非常清楚了:
- 关于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' };
},
};
console.log(arrify(obj)); // [{a: 'aa}, {b: 11}, {c: 'helloworld'}]
| 再回头看源码
if (typeof value[Symbol.iterator] === 'function') {
return [...value];
}
现在再来看这个判断条件,其实就是判断value
本身是否具有 Symbol.iterator
属性,并且类型为function
;也可以说成value
是否是可遍历的,是否拥有接口 Iterator
;还可以说成是否属于哪几种数据类型: Array、Map、Set、String、TypedArray、
函数的arguments
对象、NodeList
对象,其中Array
和String
已经单独判断就可以不考虑了。
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. 学习收获或感受
- 看似历史上最简单的一期源码阅读,结果是越看越不简单呐,越看,牵扯的知识点越多,很多自己还不是理解得很深。
- 在github代码仓库页面通过快捷键 command + k,再在文本框中输入 ">",即可查看到在线打开的命令行,确实很方便查找代码,也可以修改代码,但是我的不支持在线打开终端调试源码唉。。。
- 不得不说,别人的笔记真的好优秀哦。
- 大家在平常的业务开发中,使用Symbol了吗?一般是什么样的场景建议使用呢?有没有遇到什么坑啊?