JavaScript 迭代器

Iterator 迭代器

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

1. Iterator 作用

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

2. Iterator 遍历

1)创建一个指针对象,指向当前数据结构的起始位置;
2)第一次调用指针对象的 next 方法,将指针指向数据结构的第一个成员(包含 value 和 done 两个属性的对象);
3)第二次调用指针对象的 next 方法,将指针指向数据结构的第二个成员;
4)不断调用指针对象的 next 方法,直到它指向数据结构的结束位置。

3. Iterator 接口

interface IteratorYieldResult {
  done?: false;
  value: TYield;
}

interface IteratorReturnResult {
  done: true;
  value: TReturn;
}

type IteratorResult =
  | IteratorYieldResult
  | IteratorReturnResult;

interface Iterator {
  next(...args: [] | [TNext]): IteratorResult;
  return?(value?: TReturn): IteratorResult;
  throw?(e?: any): IteratorResult;
}

interface Iterable {
  [Symbol.iterator](): Iterator;
}

interface IterableIterator extends Iterator {
  [Symbol.iterator](): IterableIterator;
}

4. Iterator 接口实现

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function () {
      return nextIndex < array.length
        ? { value: array[nextIndex++], done: false }
        : { value: undefined, done: true };
    },
  };
}

const it = makeIterator(["a", "b"]);

it.next(); // { value: "a", done: false }
it.next(); // { value: "b", done: false }
it.next(); // { value: undefined, done: true }

5. 默认的 Iterator 接口

ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是“可遍历的”(iterable)。

Symbol.iterator 属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。

Symbol.iterator 属性名是一个表达式,返回 Symbol 对象的 iterator 属性,是一个预定义好的、类型为 Symbol 的特殊值。

如下,将对象通过 Symbol.iterator 变成一个可遍历的对象:

const obj = {
  [Symbol.iterator]: function () {
    return {
      next: function () {
        return {
          value: 1,
          done: true,
        };
      },
    };
  },
};

6. 原生可迭代对象

原生具备 Iterator 接口的数据结构如下:

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

如下,获取数组的遍历器对象:

const arr = ["a", "b", "c"];
const iter = arr[Symbol.iterator]();

iter.next(); // { value: 'a', done: false }
iter.next(); // { value: 'b', done: false }
iter.next(); // { value: 'c', done: false }
iter.next(); // { value: undefined, done: true }

关于对象(Object)未部署 Iterator 接口的原因:

对象的属性遍历顺序是无法确定的,需要开发者手动指定。遍历器本质上是一种线性处理,对于非线性的数据接口部署遍历器接口,就等于部署一种线性转换。另外对象部署遍历器接口并不是必要的,因为如果部署了就等于被当作 Map 结构使用,而 ES6 原生提供 Map 的。

7. 遍历器对象的 return(),throw()

遍历器对象除了具有 next()方法,还可以具有 return()方法和 throw()方法。如果你自己写遍历器对象生成函数,那么 next()方法是必须部署的,return()方法和 throw()方法是否部署是可选的。

return()方法的使用场合是,如果 for...of 循环提前退出(通常是因为出错,或者有 break 语句),就会调用 return()方法。
如果一个对象在完成遍历前,需要清理或释放资源,就可以部署 return()方法。

function readLinesSync(file) {
  return {
    [Symbol.iterator]() {
      return {
        next() {
          return { done: false };
        },
        return() {
          file.close();
          return { done: true };
        },
      };
    },
  };
}

8. 类数组对象

字符串、DOM NodeList 对象、arguments 对象都实现了遍历器接口,所以可用 for...of 进行遍历:

// 字符串
let str = "hello";

for (let s of str) {
  console.log(s); // h e l l o
}

// DOM NodeList对象
let paras = document.querySelectorAll("p");

for (let p of paras) {
  p.classList.add("test");
}

// arguments对象
function printArgs() {
  for (let x of arguments) {
    console.log(x);
  }
}
printArgs("a", "b");
// 'a'
// 'b'

并不是所有类似数组的对象都具有 Iterator 接口,一个简便的解决方法,就是使用 Array.from 方法将其转为数组。

const arrayLike = { length: 2, 0: "a", 1: "b" };

// 报错
for (let x of arrayLike) {
  console.log(x);
}

// 正确
for (let x of Array.from(arrayLike)) {
  console.log(x);
}

你可能感兴趣的:(JavaScript 迭代器)