源码学习—— arrify 转数组

1,247 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情 >>

这个库的代码也很简单,就一个工具函数arrify。将值转换为数组。 当我了解到这个库的作用后,我就在脑海中想,有哪些场景是会将值转为数组的?

1、默认数据兜底。实际开发中,后端返回的字段,常常会有不存在的情况,尤其是引用类型,默认值设为null、undefined。导致前端引用报错。Cannot read properties of nullCannot read properties of undefined 。这种报错相信大家在开发中经常遇到。一般我们都是在前端做默认值兜底。类似这样的代码 const a = b || [];

2、类数组转数组。作为前端开发人员,对于类数组相信都不会陌生,比如arguments、NodeList等。对这种类数组我们会有比较强的需求将其转为数组,进行数组的操作。

接下来,让我们看下这个开源库的代码。

源代码解析

整体项目结构很简洁。

image.png

查看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];
}

来看下源码中的转换规则:

  1. 针对null和undefined直接返回空数组,适用了开始我们讲的第一个场景,默认值兜底。
  2. 判断为数组类型的话,直接返回。
  3. 如果值是字符串类型,将字符串作为整体当做数组的元素。
  4. 如果是一个可迭代对象,则对其进行浅拷贝后返回一个新的数组
  5. 其他类型,直接用数组包裹后返回。

思考:

  1. 为什么字符串类型的,要作为整体当做数组的元素,它其实也是一个可迭代对象的。
  2. 可迭代对象是什么?都有哪些特征?
  3. 都有哪些方法可以判断一个对象是数组?

正常情况下,我们不会把字符串进行拆分的,一般都是当做一个整体来使用的。

判断是否是一个数组

这里复习下,都有哪些方法可以判断一个对象是数组。这里只讲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则是本次迭代的返回值。

实现自定义可迭代对象

保证以下几点,就可以实现一个可迭代对象。

  1. 实现对象的迭代器接口[Symbol.iterator](),它是一个方法。
  2. 在迭代器接口中返回一个迭代器对象。
  3. 保证迭代器对象具有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去看下,里面有很多作者的一些思考,对我们还是很有帮助的,比如这个工具库里,为什么不支持将类数组转为数组,作者就给了其这样设计的思考。 参考:

  • juejin.cn/post/710463…

  • juejin.cn/post/687345…