【若川视野 x 源码共读】第33期 | arrify转数组

72 阅读3分钟

前言

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

arrify是什么

arrify是将传入的值转化为数组,如果只是想接受单个或多个值,可以使用[singleValurOrArray].flat()代替

arrify如何使用

npm install arrify
import arrify from 'arrify';
​
arrify('🦄');
//=> ['🦄']arrify(['🦄']);
//=> ['🦄']arrify(new Set(['🦄']));
//=> ['🦄']arrify(null);
//=> []arrify(undefined);
//=> []

自己尝试去实现

看完arrify的功能后,感觉也挺简单的,就如同它的定义,只要在函数内做好各种数据类型的校验即可,自己尝试动手去写,后来发现我能想到的场景也只有常见的基本类型和引用类型,es6新增的Map、Set没能考虑进去...

后来在翻看源码的时候,发现源码里有一个很精妙的判断typeof value[Symbol.iterator] === 'function',非常巧妙的解决了更多类型的校验,另外单独把string类型单独拿出来做了判断,心里就很好奇为啥是string,而不是number、boolean等,这个也触及到了自己的知识盲区。

arrify源码解读

export default function arrify(value) {
    // 如果value为null、undefined,返回[]
    if (value === null || value === undefined) {
        return [];
    }
​
    // 如果是数组,直接返回value
    if (Array.isArray(value)) {
        return value;
    }
​
    // 如果是字符串,返回[value]
    // 单独校验string是因为其原型上有迭代器,是返回每个字符
    if (typeof value === 'string') {
        return [value];
    }
    
    // 如果value的原型上有迭代器,则使用...解构其值
    if (typeof value[Symbol.iterator] === 'function') {
        return [...value];
    }
​
    return [value];
}

Symbol.iterator

Symbol.iterator 为每一个对象定义了默认的迭代器。该迭代器可以被for...of循环使用。

定义

JavaScript原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of使用。

内部数据构成

在JavaScript中,迭代器是能调用next方法实现迭代的一个对象,该方法返回一个具有两个属性的对象。

  • value:可迭代对象的下一个值
  • done:表示是否已经取出所有的数据了。false表示还有数据,true表示后面已经没有数据了。

手写迭代器

在写迭代器之前,我们先来看看数据的迭代器是什么样子

Snipaste_2022-11-21_23-49-10.jpg

我们可以看到,数组的迭代器是其原型上的一个可执行函数。我们执行一下这个方法,看看执行器返回的结果

Snipaste_2022-11-22_23-19-13.jpg

迭代器的执行结果是一个Array Iterator实例对象,其原型上有个next方法,我们再执行一下这个方法,看看next的执行结果是啥

Snipaste_2022-11-22_23-27-12.jpg

我们可以看到next返回了数组每一项的值value和是否有下一项的标识done

知道了迭代器的原理和返回值,那我们再实现其原理就会得心应手了

class MyArrayIterator {
  constructor (arr) {
    this.arr = arr;
    this.index = 0;
  }

  next() {
    var length = this.arr.length;
    if(this.index < length) {
      return { value: this.arr[this.index++], done: false }
    } else {
      return {value: undefined, done: true }
    }
  }
}

Array.prototype[Symbol.iterator] = function () {
  return new MyArrayIterator(this)
}

var arr = ['a', 'b', 'c'];

var myArrIterator = arr[Symbol.iterator]();
myArrIterator.next(); // { value: 'a', done: false }
myArrIterator.next(); // { value: 'b', done: false }
myArrIterator.next(); // { value: 'c', done: false }
myArrIterator.next(); // { value: undefined, done: true }

目前,原生JavaScript支持Iterator的数据结构如下

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的arguments对象
  • NodeList对象

使用场景

  1. 解构赋值

对数组和Set结构进行解构赋值时,会默认调用Symbo.irerator方法

  1. 扩展运算符
  1. 其他场景

    • for...of
    • Array.form()
    • Map(),Set(),WeakMap(),WeakSet()
    • Promise.all()
    • promise.race()

我们都知道,Object并不支持for..of来遍历对象的每个属性值,我们可以给对象的原型对象设置迭代器来实现,感兴趣的可以尝试一下。

参考:

blog.csdn.net/chilanzi/ar… juejin.cn/post/716467… juejin.cn/post/716130… blog.csdn.net/qq_19901795…