-
本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
-
这是源码共读的第33期,链接:juejin.cn/post/710021…
-
算是头一次参加若川源码共读在掘金的活动,唯一的期望就是打开自己学习源码的思路、以及打开自己的视野,后续进行不断的优化和学习
-
真正的热爱学习,多参与、多查阅文档、多思考、形成自己的思维思想
-
如果或许对于你有一点点启发,那么也算是另外的一份收获
-
最后希望自己能坚持下去
-
本文阅读思路提示
- 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文件
"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、从入口文件的入口函数开始
终于找到入口文件和入口函数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 ) /// function
| 我们对下面的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、拓展一下这个es5 迭代协议
let someString = "hi";
typeof someString[Symbol.iterator]; // "function"
可以看到string内置了Symbol.iterator这个属性,是一个方法。
let iterator = someString[Symbol.iterator]();
iterator + ""; // "[object String Iterator]"
iterator.next(); // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }
这个写法是不是很眼熟,如果了解过生成器函数,应该就能看懂了。 我们顺便来写一个可迭代对象吧
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable]; // [1, 2, 3]
for ( let item of myIterable){
console.log(item)
}
// 1
// 2
// 3
[...myIterable]
//(3) [1, 2, 3]
//可见可以通过生成器函数,实现可迭代对象,
我们可以通过提供自己的 @@iterator 方法,重新定义迭代行为:
// 必须构造 String 对象以避免字符串字面量 auto-boxing
var someString = new String("hi");
someString[Symbol.iterator] = function() {
return { // 只返回一次元素,字符串 "bye",的迭代器对象
next: function() {
if (this._first) {
this._first = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_first: true
};
};
我们再写一个简单的迭代器
function makeIterator(array) {
let nextIndex = 0;
return {
next: function () {
return nextIndex < array.length ? {
value: array[nextIndex++],
done: false
} : {
done: true
};
}
};
}
let it = makeIterator(['哟', '呀']);
console.log(it.next().value); // '哟'
console.log(it.next().value); // '呀'
console.log(it.next().done); // true
使用生成器
function* makeSimpleGenerator(array) {
let nextIndex = 0;
while(nextIndex < array.length) {
yield array[nextIndex++];
}
}
let gen = makeSimpleGenerator(['哟', '呀']);
console.log(gen.next().value); // '哟'
console.log(gen.next().value); // '呀'
console.log(gen.next().done); // true
function* idMaker() {
let index = 0;
while (true) {
yield index++;
}
}
let gen = idMaker();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
// ...
1.看到这里的小伙伴如果不了解迭代器的,估计也已经又看蒙了。
2.生成器对象到底是一个迭代器,还是一个可迭代对象?
let aGeneratorObject = function* (){
yield 1;
yield 2;
yield 3;
}();
typeof aGeneratorObject.next;
// 返回"function", 因为有一个 next 方法,所以这是一个迭代器
typeof aGeneratorObject[Symbol.iterator];
// 返回"function", 因为有一个 @@iterator 方法,所以这是一个可迭代对象
aGeneratorObject[Symbol.iterator]() === aGeneratorObject;
// 返回 true,因为 @@iterator 方法返回自身(即迭代器),所以这是一个格式良好的可迭代对象
[...aGeneratorObject];
// 返回 [1, 2, 3]
console.log(Symbol.iterator in aGeneratorObject)
// 返回 true,因为 @@iterator 方法是 aGeneratorObject 的一个属性
强烈建议大家读一下高程4第七章节,顺便再看下不可枚举属性。
总结:
1.迭代器对象的概念实现了正式的`Iterable`接口,而且可以通过迭代器`Iterator`消费。
2.任何实现`Iterable`接口的数据结构都可以被实现`Iterator`接口的结构“消费”(consume)。**迭代器**(iterator)是按需创建的一次性对象。每个迭代器都会关联一个**可迭代对象**,而迭代器会暴露迭代其关联可迭代对象的API。迭代器无须了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值。这种概念上的分离正是`Iterable`和`Iterator`的强大之处。
3.可迭代协议
实现`Iterable`接口(可迭代协议)要求同时具备两种能力:支持迭代的自我识别能力和创建实现`Iterator`接口的对象的能力。
在ECMAScript中,这意味着必须暴露一个属性作为“默认迭代器”,而且这个属性必须使用特殊的`Symbol.iterator`作为键。
这个默认迭代器属性必须引用一个迭代器工厂函数,调用这个工厂函数必须返回一个新迭代器。
let num = 1;
let obj = {};
// 这两种类型没有实现迭代器工厂函数
console.log(num[Symbol.iterator]); // undefined
console.log(obj[Symbol.iterator]); // undefined
let str = 'abc';
let arr = ['a', 'b', 'c'];
let map = new Map().set('a', 1).set('b', 2).set('c', 3);
let set = new Set().add('a').add('b').add('c');
let els = document.querySelectorAll('div');
// 这些类型都实现了迭代器工厂函数
console.log(str[Symbol.iterator]); // f values() { [native code] }
console.log(arr[Symbol.iterator]); // f values() { [native code] }
console.log(map[Symbol.iterator]); // f values() { [native code] }
console.log(set[Symbol.iterator]); // f values() { [native code] }
console.log(els[Symbol.iterator]); // f values() { [native code] }
// 调用这个工厂函数会生成一个迭代器
console.log(str[Symbol.iterator]()); // StringIterator {}
console.log(arr[Symbol.iterator]()); // ArrayIterator {}
console.log(map[Symbol.iterator]()); // MapIterator {}
console.log(set[Symbol.iterator]()); // SetIterator {}
console.log(els[Symbol.iterator]()); // ArrayIterator {}
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); //正常的数组
});
测试用例本身并没有什么花期呼哨的边界值