携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情 >>
- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 这是源码共读的第33期,链接:juejin.cn/post/710021…
这个库的代码也很简单,就一个工具函数arrify。将值转换为数组。 当我了解到这个库的作用后,我就在脑海中想,有哪些场景是会将值转为数组的?
1、默认数据兜底。实际开发中,后端返回的字段,常常会有不存在的情况,尤其是引用类型,默认值设为null、undefined。导致前端引用报错。Cannot read properties of null
、Cannot read properties of undefined
。这种报错相信大家在开发中经常遇到。一般我们都是在前端做默认值兜底。类似这样的代码 const a = b || []
;
2、类数组转数组。作为前端开发人员,对于类数组相信都不会陌生,比如arguments、NodeList等。对这种类数组我们会有比较强的需求将其转为数组,进行数组的操作。
接下来,让我们看下这个开源库的代码。
源代码解析
整体项目结构很简洁。
查看package.json,我们知道入口文件是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];
}
来看下源码中的转换规则:
- 针对null和undefined直接返回空数组,适用了开始我们讲的第一个场景,默认值兜底。
- 判断为数组类型的话,直接返回。
- 如果值是字符串类型,将字符串作为整体当做数组的元素。
- 如果是一个可迭代对象,则对其进行浅拷贝后返回一个新的数组
- 其他类型,直接用数组包裹后返回。
思考:
- 为什么字符串类型的,要作为整体当做数组的元素,它其实也是一个可迭代对象的。
- 可迭代对象是什么?都有哪些特征?
- 都有哪些方法可以判断一个对象是数组?
正常情况下,我们不会把字符串进行拆分的,一般都是当做一个整体来使用的。
判断是否是一个数组
这里复习下,都有哪些方法可以判断一个对象是数组。这里只讲2个最靠谱的方法。
用Object的toString来判断
很常用的方法。可以判断任何类型。也就是Object.prototype.toString()。这个方法可以返回[object type]
。其中type代表的就是数据类型。
const ary = [1, 2];
Object.prototype.toString.call(ary);
// 返回值 '[object Array]'
这里不深入探究,只要记住只有Object这个对象的原型上的toString方法才会返回对象的类型。因此需要借用call或apply方法来改变调用的上下文,以此达到使用Object.prototype上的toString方法。
用Array的isArray方法来判断
这个方法是ES5提供的用来判断一个对象是不是一个数组。
const array = [1, 2];
const str = '';
Array.isArray(ary); // true
Array.isArray(str); // false
很方便的一个方法,而且很靠谱,除了有一点兼容问题,不过现在来看直接使用问题也不大,目前基本也都是现代浏览器了。
Symbol.iterator 可迭代对象
首先先来了解什么是可迭代对象。在ES6中引入了迭代器和可迭代对象的概念,且提供了对可迭代对象的相关支持,例如for...of循环、Map、Set、展开语法...等。这样让就简化了我们对数组外的数据集合的遍历操作。 要成为一个可迭代对象,一个对象必须实现@@iterator方法。也就是说对象或其原型链上必须有一个键为@@iterator的属性,可以通过常量Symbol.iterator访问该属性:
[Symbol.iterator]: 一个无参数的函数,其返回值为一个符合迭代器协议的对象。
当一个对象需要被迭代的时候,先调用它的@@iterator方法,然后使用此方法返回的迭代器对象获得要迭代的值。
根据规范,迭代器对象必须必须实现next()
方法。返回当前元素并将迭代器指向下一个元素,返回的对象格式{done: true | false, value: any}
。done是一个布尔值,表示迭代是否结束,value则是本次迭代的返回值。
实现自定义可迭代对象
保证以下几点,就可以实现一个可迭代对象。
- 实现对象的迭代器接口
[Symbol.iterator]()
,它是一个方法。 - 在迭代器接口中返回一个迭代器对象。
- 保证迭代器对象具有
next()
方法,并且返回{value: any, done: boolean}
的结构。
const customIterable = {
0: 'A',
1: 'B',
2: 'C',
length: 3,
[Symbol.iterator]: function () {
return {
index: 0,
souceData: this,
next() {
if (this.index >= this.souceData.length) {
return {
done: true,
}
} else {
return {
done: false,
value: this.souceData[this.index++]
}
}
}
}
}
}
for(const item of customIterable) {
console.log(item); // 依次打印 A B C
}
这里注意this上下文的使用。在next()是无法访问到可迭代对象的this,这里我们可以将this赋值到@@iterator()方法返回的对象中,这样在next()中就可以通过取本身对象的值来操作可迭代对象的属性了。
类数组
所谓的类数组对象,就是可以通过索引属性访问元素并且拥有length属性的对象,但不可以使用数组的方法。比如arguments、NodeList等。
如何将一个类数组转为数组,这里介绍一种ES6的方法Array.from()。对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
比如:
const obj = {
0: 'A',
1: 'B',
length: 2,
};
Array.from(obj); // ['A', 'B']
本次学习的arrify其实不具备将类数组转为数组的能力的。作者在其仓库的issue也有做解释。github.com/sindresorhu… 。这个是作者故意限制的。假设用户实际上是希望将类数组当做数组中的元素的要求。api要保持单一原则,如果用户想把类数组转成数组,可以使用ES6中的Array.from()。
工具库了解
在package.json中,我们看到有一个test脚本"test": "xo && ava && tsd"
。
看到该库使用了以下3个开发依赖:
- ava: Node.js 的测试运行器,具有简洁的 API、详细的错误输出、新的语言特性和进程隔离。自动执行根目录下的test.js文件。
- xo: 一个代码风格校验工具,内部是基于ESLint实现的,封装了lint规则,无需配置.eslintrc等配置文件。
- tsd: 用来检查 TypeScript 类型定义。通过创建扩展名为.test-d.ts 的文件来为您的类型定义(即 .d.ts 文件)编写测试。
收获
基础知识的复习
- 类数组对象的学习,并学习Array.from()将类数组转为数组
- 可迭代对象的学习,如何实现一个自定义的可迭代对象。了解可迭代协议和迭代器协议。
- 判断一个对象是否是一个数组
依赖包学习
- xo: 无需配置的lint检查工具。
- ava: 一个node环境下的测试工具。
- tsd: 第一次了解到我们写的类型文件是可以进行测试的。
其他
-
可以去开源库的issue去看下,里面有很多作者的一些思考,对我们还是很有帮助的,比如这个工具库里,为什么不支持将类数组转为数组,作者就给了其这样设计的思考。 参考: