持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
hey, 我是小黄瓜没有刺。一枚菜鸟瓜🥒,期待关注➕ 点赞,共同成长~
本系列会精读一些小而美的开源作品或者框架的核心源码。
arrify可以将把一个值转换为一个数组。
基本使用 👾
1. 安装
npm i arrify
2. 使用
arrify('🦄');
//=> ['🦄']
arrify(['🦄']);
//=> ['🦄']
arrify(new Set(['🦄']));
//=> ['🦄']
arrify(null);
//=> []
arrify(undefined);
//=> []
arrify('小黄瓜');
//=> ['小黄瓜']
实现 ✨
先来看一下arrify的测试:
import test from 'ava';
import arrify from './index.js';
test('main', t => {
t.deepEqual(arrify(null), []);
t.deepEqual(arrify(undefined), []);
t.deepEqual(arrify('foo'), ['foo']);
t.deepEqual(arrify(new Map([[1, 2], ['a', 'b']])), [[1, 2], ['a', 'b']]);
t.deepEqual(arrify(new Set([1, 2])), [1, 2]);
const fooArray = ['foo'];
t.is(arrify(fooArray), fooArray);
});
js中有许多不同的类型,比如基本类型有 String、Boolean、Number、Symbol、BigInt、Undefined、Null、Object Object 中又能扩展出 Date、Function、RegExp、Array、Map、Set、自定义对象.....
arrify根据传入的不同类型的值进行不同的处理
由此我们可以得到 arrify 的转换规则:
- Null 和 Undefined 返回空数组
- 数组不需要转换,直接返回
- String 将整体作为数组元素返回
- 可迭代对象,浅拷贝直接创建新数组
- 其他类型直接返回被数组包裹
满足传入null或者undefined
t.deepEqual(arrify(null), []);
首先定义一个arrify导出函数:
export default function arrify(value) {
}
然后判断是否为null或者undefined
if(value === null || value === undefined) {
return []
}
处理value为数组的情况,直接返回
if(Array.isArray(value)) {
return value
}
判断是否为string类型的情况,直接将整段字符串作为数组的某一项的值进行包裹
if(typeof value === 'string') {
return [value]
}
接下来是 Map 和 Set 等可迭代对象,那么问题来了,什么是可迭代对象?怎么样进行判断并处理呢?
可迭代对象
可迭代对象就是一个对象在内部部署了遍历器(Iterator),这就代表这个对象是可以被遍历的。 Iterator 的作用:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;
Iterator 的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。
例如:
const obj = {
// 直接在对象中定义迭代器函数
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
使用 object 对象结构来创建 Symbol.iterator 属性实现遍历对象:
const obj = {
// 定义数据
a: 1,
b: 2,
c: 3,
length: 3,
// 定义迭代器函数
[Symbol.iterator]: function() {
// 初始化遍历次数
let index = 0
// 获取对象值
let data = this
return {
// 返回遍历函数,每次判断是否已经遍历完成(index是否大于等于长度)
next(value) {
// 遍历完成done返回true
if(index >= data.length) {
return {
done: true
}
// 如果没有完成value值返回当前遍历的值
} else {
return {
done: false,
value: data[index++]
}
}
}
}
}
}
而原生具备 Iterator 接口的数据结构如下:
Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象
而arrify中实现对Map以及Set等可迭代对象的判断就借用了迭代器的这一特性:
// 判断value中是否存在Symbol.iterator,并且类行为function
// 如果命中则将value解构生成一个新的数组并返回
if (typeof value[Symbol.iterator] === 'function') {
return [...value];
}
可迭代对象本身或原型链上实现了 [Symbol.iterable] 方法,可以根据 typeof obj[Symbol.iterable] === "function" 判断是否是可迭代对象。
最后完整代码如下:
export default function arrify(value) {
// value值为numm/undefined时,返回空数组
if (value === null || value === undefined) {
return [];
}
// value值为数组时,直接将value返回
if (Array.isArray(value)) {
return value;
}
// value值为string时,直接将整个string作为数组的一项进行包裹
if (typeof value === 'string') {
return [value];
}
// value值为可迭代对象时时,将value解构成一个新数组返回
if (typeof value[Symbol.iterator] === 'function') {
return [...value];
}
// 如果都没有命中,则直接对value进行包裹返回
return [value];
}
彩蛋 🎉
最后一个小彩蛋 arrify源码中的ts类型校验:
export default function arrify<ValueType>(
value: ValueType
): ValueType extends (null | undefined)
? [] // eslint-disable-line @typescript-eslint/ban-types
: ValueType extends string
? [string]
: ValueType extends readonly unknown[]
? ValueType
: ValueType extends Iterable<infer T>
? T[]
: [ValueType];
本文参考
es6.ruanyifeng.com/#docs/itera…
写在最后 ⛳
源码系列第一篇!未来可能会更新实现mini-vue3和javascript基础知识系列,希望能一直坚持下去,期待多多点赞🤗🤗,一起进步!🥳🥳