源码共读,第33期 | arrify 转数组

220 阅读4分钟

前言

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

本篇是源码共读第33期 | arrify转数组,点击了解本期详情

准备

首先arrify是什么?官方说明是将传入的值转换为数组,同时也给出了几个样例

import arrify from 'arrify';

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

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

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

arrify(null);
//=> []

arrify(undefined);
//=> []
  1. 拉取代码
git clone https://github.com/sindresorhus/arrify.git
  1. 安装依赖
npm i
  1. 查看package.json依赖
"scripts": {
    "test": "xo && ava && tsd"
},
"devDependencies": {
    "ava": "^3.15.0",
    "tsd": "^0.14.0",
    "xo": "^0.39.1"
}
  • ava,基于Node.js 的单元测试包,支持Typescript
  • tsd,通过.test-d.ts 文件做类型定义检查,它们不会被编译测试,只会对类型定义进行静态分析
  • xo,集成了linter规则的ESLint包,不需要再做额外配置

那么执行npm run test 后做了什么呢?

  1.  xo 对 js 和 ts 文件做 规则检查
  2. ava 跑 test.js 测试 index.js
  3. tsd 跑 index.test-d.ts 测试 index.d.ts

看源码前的思考

如果让我完成这个需求,我会考虑哪些点,会从哪块入手,如何编写此功能? 首先肯定会根据数据类型来,那数据类型有哪些呢?基本数据类型(Number,String,Boolean,Undefined,Null,还有之后新增的BigInt以及Symbol),引用数据类型(Object,Array,Function,以及RegExpDate)。

先检查是否为null和undefined,如果是直接返回个空数组,其次是如果传入的就是个数组那就直接原封不对返回即可,其他的用数组包裹返回出去,重点就在其他的如何界定,如何去判断呢?我先想到的是判断类型,用typeof判断类型是哪种然后再返回对应的,但是这样判断太多了,就被我pass掉了然后又没想到新方案,带着这个疑问来查看了源码(其实是也只能想到这么多了😂)

源码

由于我实在搞不懂单元测试啥的,这知识怎么都进不了脑子,所以干脆专注源码好了,单元测试什么的等知识进脑子了我再回来补充吧

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

嗯,首先判断了nullundefined和我想的差不多,接下来是数组,哎又对上了看了思路是对的。接下来是string,难道要一个个判断吗?嗯?Symbol.iteratorfunction?这是什么?

Symbol.iterator

说实话要不然看这个源码,我都忘记迭代器是什么了😂唉忘光了。简单来说它为每一个对象定义了默认的迭代器,该迭代器可以被 for...of 循环使用。像String,ArrayMapSet这些就是默认就具有这种迭代器的,换句话说,这个对象需要有一个属性,并且属性名为Symbol.iterator,下面编写下代码具体看下

let arr = new Array();
arr = [1, 2, 3, 4];
console.log(arr);
图片说明

打印下这个属性

console.log(arr[Symbol.iterator]);
// => ƒ values() { [native code] }

看到返回一个函数,那我们再调用下这个函数

console.log(arr[Symbol.iterator]());
图片说明

哎里面还有个next方法,那我们再调用一下吧

console.log(arr[Symbol.iterator]().next());
// => { value: 1, done: false }

可以看到返回了一个含有valuedone属性的对象

  • value ,迭代器返回的当前值,done为true则为undefined
  • done,是不是已经到了结尾,是一个布尔值,如果为true则意味着迭代结束

生成迭代器函数

function iteratorFn(objIterator) {
    let index = 0;
    return {
        next: function () {
            if (index < objIterator.length) {
                return { done: false, value: objIterator[index++] };
            } else {
                return { done: true, value: undefined };
            }
        },
    };
}

到这倒是明白了typeof value[Symbol.iterator] === 'function'但是不明白为什么要把string类型单独拿出去写,然后在共读群里问了下,若川大佬解答说是字符串扩展时候就不对了。嗯?还有我没看到的地方?好,继续看。哎,不用看了,是因为判断体里面写的是return [...value]服了自己了

for...of关键字和 扩展运算符...,它的本质就是迭代器的语法糖,只有具有可迭代的特性才能使用。好了现在总结下源码逻辑

  1. Null 和 Undefined 返回空数组
  2. 如果是数组不必再转换,返回本身
  3. 判断String 让其保持整体作为数组元素存在
  4. 可迭代对象,浅拷贝迭代值创建新数组
  5. 其他类型直接被数组包裹返回

总结

  1. 学习arrify源码
  2. 复习Symbol.iterator

不足之处:

  1. 个人平时编写代码时没有针对写过测试用例,所以整体流程还是有些懵,只会看源码