ES6 Iterator

简单谈谈

Iterator(遍历器)是一种机制:当外部访问所属对象时,提供统一的访问机制。任何的数据结构一旦部署了 Iterator 接口,就相当于实现了统一的遍历机制,使得外部程序在使用这些不同的数据结构时,有一致的写法。

具体到语言层面,需要实现了以下内容:

  1. 对象下需要部署 Symbol.iterator 方法。
  2. 该方法需要返回一个含有 next 方法的对象:{ next: () => { value: any, done: boolean } }`。
  3. 该对象的 next 方法需要有统一的返回结构:{ value: any, done: boolean }

部署了上述特性的对象,就叫做:可遍历对象。在 ES6 中,MapSetArrayString 等的实例都具备上述结构。但并非仅有这些类的实例时可遍历对象,只要是对象有上述特性,就是可遍历对象,因此可遍历对象是可以由我们自己创建的。

Iterator 接口

ES6 规定,默认的 Iterator 接口部署在对象的 Symbol.iterator 属性上,当该属性有值时,该对象即是可遍历对象。

一个简单的可遍历对象:

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

obj 对象下部署了 Symbol.iterator 方法,那么该对象即是可遍历对象,并如上述所说,执行该方法会获得一个具有 next 属性的对象,调用返回对象的 next 方法,即可获得 {value: any, done: boolean} 结构的数据。

常见原生就具备 Iterator 接口的对象:

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

可以用以下方法确定某个对象是否具备 Iterator 接口。

let arr = ['a', 'b', 'c'];
let 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 }

for...of

如果一个对象是可遍历对象,那么该对象就可以被 for...of 循环调用,反过来说就是,如果一个对象可被 for...of 循环,那么该对象一定拥有 Symbol.iterator 方法,并且该方法能返回符合要求的结果。

对数组调用 for...of 循环。

let arr = ['a', 'b', 'c']

for(let value of arr){
    console.log(value) // a b c
}

实现自定义的 Iterator 接口,并调用 for...of 循环。

class RangeIterator {
    constructor(start, stop) {
        this.value = start;
        this.stop = stop;
    }

    [Symbol.iterator]() {
        return this
    }

    next() {
        let value = this.value;
        if (value < this.stop) {
          this.value++
          return {
            done: false,
            value
          }
        }
        return {
            done: true,
            value: undefined
        }
    }
}

for (let value of new RangeIterator(0, 3)) {
    console.log(value)
    // 0, 1, 2
}

遍历过程

当有方法或者动作需要进行遍历对象的时候(触发 Symbol.iterator),会进行以下步骤:

  1. 调用 Symbol.iterator 方法,获得 next 函数。
  2. 调用 next 方法,获得返回值。
  3. 判断返回值中的 done 属性,若为 true 则停止循环。
  4. 获得返回值中的 value 属性并操作,操作完成后回到步骤 2

调用场合

解构赋值

let set = new Set().add('a').add('b').add('c');

let [x, y] = set;               // x = 'a'; y = 'b'

let [first, ...rest] = set;     // first = 'a'; rest = ['b', 'c']

使用扩展运算符

let str = 'hello';
[...str];                       //  ['h','e','l','l','o']

let arr = ['b', 'c'];
['a', ...arr, 'd'];             // ['a', 'b', 'c', 'd']

for...of

let array = [1, 2, 3];
for (let vaue of array) {
    // do something
}

Map & Set

let set = new Set([1, 2, 3, 4]);
let map = new Map([['a', 1], ['b', 2]]);

Array.from

let set = new Set([1, 2, 3, 4]);
let array = Array.from(set);

Promise.all & .race

let pAll = Promise.all([ /*some promise*/ ]);
let pRace = Promise.race([ /*some promise*/ ]);

遍历器的 return

遍历器除了拥有 next 方法,还可以拥有 return 方法, return 方法主要用于循环中途退出的情况,用于释放内存,以及及时停止遍历器。

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

// 情况一:由 break 触发 return
for (let line of readLinesSync(fileName)) {
    console.log(line)
    break
}

// 情况二:由 error触发 return
for (let line of readLinesSync(fileName)) {
    console.log(line)
    throw new Error()
}