【源码漫游】 arrify | 如何将一个值转化为数组

271 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

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

hey, 我是小黄瓜没有刺。一枚菜鸟瓜🥒,期待关注➕ 点赞,共同成长~

本系列会精读一些小而美的开源作品或者框架的核心源码。

arrify可以将把一个值转换为一个数组。

image.png

基本使用  👾

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中有许多不同的类型,比如基本类型有 StringBooleanNumberSymbolBigIntUndefinedNullObject Object 中又能扩展出 DateFunctionRegExpArrayMapSet、自定义对象.....

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

接下来是 MapSet 等可迭代对象,那么问题来了,什么是可迭代对象?怎么样进行判断并处理呢?

可迭代对象

可迭代对象就是一个对象在内部部署了遍历器(Iterator),这就代表这个对象是可以被遍历的。 Iterator 的作用:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;

Iterator 的遍历过程是这样的。

(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含valuedone两个属性的对象。其中,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-vue3javascript基础知识系列,希望能一直坚持下去,期待多多点赞🤗🤗,一起进步!🥳🥳