JavaScript – Iterator

参考

阮一峰 – Iterator 和 for...of 循环

 

前言

es6 以后经常可以看到 for...of 的踪迹. 如果你细看会发现它挺神奇的. 不只是 Array 可以被 for...of, 有些对象也可以被 for...of.

const array = [1, 2, 3];
console.log(Array.isArray(array)); // true
for (const value of array) {
  console.log(value); // 1, 2, 3
}

上面 Array 能被 for...of

下面 isArray 是 false, querySelectorAll 返回的是一个 NodeListOf 对象. 但是依然可以被 for...of.

const notArray = document.querySelectorAll('h1');
console.log(Array.isArray(notArray)); // false
for (const value of notArray) {
  console.log(value); // h1
}

其原因是, 只要是 Iterable 对象就可以被 for...of 遍历. 而 Array 和 NodeListOf 都属于 Iterable 对象, 所以它们都可以被遍历.

 

Iterable 对象

这是 TypeScript 对 Iterable 对象的描述

interface Iterable<T> {
    [Symbol.Iterator](): Iterator<T>;
}

一个对象里有一个 Symbol.Iterator 方法, 就是一个 Iterable 对象. 之前在 JavaScript – Symbol 里, 我有提到了 es6 如何利用各种 build-in Symbol 来做许多事情, 其中一个就是 Iterator 了.

不只 JS, C# 也是有 Iterator 的概念. List<string> 的 List 就是一个 Iterable 对象来的, 所以它支持 foreach.

 

Iterator

Iterable 对象的 Symbol.Iterator 方法必须返回一个 iterator. 

这个 Iterator 也是一个对象, 它有一个最重要的方法 next, 下面我们来看看 Iterator 对象怎么用.

 

How to use a Iterator

const Iterator: Iterator<string> = {
  next() {
    return { value: '1', done: true };
  },
};
const { value, done } = iterator.next();
console.log(value); // '1'
console.log(done); // true

虽然我用 TypeScript 写, 但是你可以把它当 JavaScript 看, 去掉你看不懂的地方就可以了. 不影响理解的.

next 方法返回一个 value 和一个 boolean done 表示遍历是否完结了.

站在一个使用 Iterator 的角度, 我们可以了解到.

1. Iterator 是对象, 有一个 next 方法

2. 调用 next 可以拿到一个值, 同时可以知道是否遍历结束. 如果还没有结束意味着可以继续 next 拿到下一个值.

3. Iterator 内部如何实现遍历, 调用者是不知道的, 可能内部是一个 array 或者是一个 formula. 消费者不知道, 也不需要知道. 只要 call next 就对了.

 

for...of

了解了如何使用 Iterator, 就能理解 for...of 的运作逻辑了.

它只是封装了调用 .next 和判断 done 是否结束.

看看这个例子

const stringList: Iterable<string> & { value: string } = {
  value: 'Derrick',
  [Symbol.Iterator]() {
    let index = 0;
    return {
      next: () => ({
        value: this.value[index++],
        done: index === this.value.length + 1,
      }),
    };
  },
};

for (const value of stringList) {
  console.log('value', value); // d e r r i c k
}

stringList.value = 'super';

for (const value of stringList) {
  console.log('value', value); // s u p e r
}

stringList 是一个 Iterable, 一个拥有 Symbol.Iterator 方法的对象

Symbol.Iterator 返回一个 Iterator, 一个拥有 next 方法的对象. next 返回一个值和 done (表示是否遍历结束)

for...of 开始时, 会调用 Symbol.Iterator 方法得到 stringList 的 Iterator

接着调用 next 获取值放入 value, 这时 console 就会发出每一个 char 值. 这个逻辑写在 Iterator 内. for...of 并不知道 value 的由来, 它只是消费.

当 for...of 发现 done = true 时, 遍历就结束了.

 

Break and Return

在 for..of 内使用 break 会中断遍历, Iterator 的 return 方法会被调用.

Iterator 除了 next 方法, 还可以实现 return 和 throw 方法. throw 是配合 Generator 函数才会用到, 这篇先不提. 

return 的用意是, 当调用者中断遍历时通知 iterator. 为了让 Iterator 内部做一些释放内存之类的事情.

所以一般的流程是 Iterable[Symbol.Iterator]() 获得 Iterator, 然后 .next 一直到结束

中断的情况则是调用了 next, 但在还没有 done 之前调用了 return.

for...of 内的 break 语句就会调用 iterator.return (注意: 只有 break 才是中断遍历, continue 并不是哦)

注: iterator.return 是可以传参数和返回值的, 但这个用于 Generator 函数. 一般 for...of 遍历不需要.

const stringList: Iterable<string> & { value: string } = {
  value: 'Derrick',
  [Symbol.Iterator]() {
    let index = 0;
    return {
      next: () => ({
        value: this.value[index++],
        done: index === this.value.length + 1,
      }),
      return: (value) => {
        console.log('return', value);
        return {
          value: '',
          done: true,
        };
      },
    };
  },
};

for (const value of stringList) {
  if (value === 'i') {
    console.log('calling break');
    break;
  }
  console.log('value', value); // d e r r i calling break return
}

 

Iterable + Spread Operator

Iterable > Iterator > next > values

这样步骤太多了, 有一个非常快捷的方法可以把所以 values 遍历放入 Array 中.

那就是配上 Spread Operator

const values = [...stringList]; // ['D', 'e', 'r', 'r', 'i', 'c', 'k']

 

posted @ 2022-05-13 13:49  兴杰  阅读(268)  评论(0编辑  收藏  举报