【源码共读】从arrify的源码中简单复习迭代器

87 阅读3分钟

【源码共读】从arrify的源码中简单复习迭代器

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

这是源码共读的第33期

前言

本文是之前写的,只放在了个人博客里,没有发在掘金上

github仓库地址 arrify

这个arrify包的功能就是把一个值转化为数组形式。

开胃菜

export default function arrify(value) {
    // 如果值是null或undefined,直接返回空数组
    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];
}

上面的代码就是本次的源码,只有短短十几行,大概这就是“浓缩的都是精华”吧。上面已经简单地注释了一下。

但是,有两个部分没有任何注释,而这就是本文的核心所在。

先留点悬念:value的类型是string时也是返回[value],最后返回的也是[value],为什么不能共用最后的return [value];呢?(懂的大佬请假装不懂🐶,或者纯当复习)

迭代器

上面为什么不共用最后的return [value];就是因为String内置了Iterator接口,也就是说此时value[Symbol.iterator]的类型就是函数,所以,如果共用,那么字符串就会走进去最后的if块里。

那么,这个Iterator接口究竟是什么东西呢?

这个就是迭代器接口,调用该接口,就会返回一个迭代器对象(也可称遍历器对象)。有该接口的数据结构都会有Symbol.iterator属性。

那么,接下来就来简单试验一下(玩一下)。

const str = 'Hello';

const gen = str[Symbol.iterator]();

console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());

可以看到我们调用Symbol.iterator方法得到迭代器对象,调用next()方法就能遍历该字符串。同理,内置的其他具备Iterator接口的数据结构也能够通过该方法遍历。

如:

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

我们也可以自己实现一个迭代器接口,从而能够自定义遍历过程。

const obj = {
  count: 0,
  [Symbol.iterator]() {
    const self = this;

    return {
      next() {
        if (self.count < 5) {
          return { value: self.count++, done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    }
  }
};

let gen = obj[Symbol.iterator]();

console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());

扩展运算符的作用

上面我们是通过调用迭代器接口,得到遍历器对象,然后调用遍历器对象的next()方法来实现遍历。所以如果我们想让有Iterator接口的数据接口转换成数组就很麻烦。

但是实际上我们使用扩展运算符就能很简单的将可迭代对象转换成数组。

const str = 'Hello';
console.log([...str]);    // [ 'H', 'e', 'l', 'l', 'o' ]

const set = new Set([1, 2, 3, 2, 1, 4]);
console.log([...set]);    // [ 1, 2, 3, 4 ]

从上面的例子也能看出为什么将字符串和其他的可迭代对象分开。如果不分开,那么得到的数组将会是将字符串拆解后的数组,实际上意义并不大。如果想要这种效果,也可以单独使用扩展运算符处理,毕竟工具函数不可能考虑所有的使用情境。

测试源码

import arrify from "arrify";

console.log(arrify(undefined));
console.log(arrify(null));

console.log(arrify('Hello'));
console.log(arrify([1, 2, 3, 4]));
console.log(arrify(new Set([1, 2, 3, 2, 1, 4])));
console.log(arrify(new Map([
  ['name', 'clz'],
  ['age', '21']
])));