ES6 - Iterator和Generator

1,011 阅读4分钟

Iterator:遍历器

为某种数据结构提供的一种统一的遍历机制

Iterator的遍历过程如下:

  1. 创建一个指针对象,指向当前数据结构的起始位置。遍历器对象本质上是一个指针对象
  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
  3. 第n次调用指针对象的next方法,指针就指向数据结构的第二个成员
  4. 不断调用next方法,直至指向数据结构的结束位置

每次调用next方法,返回一个包含数据结构当前成员信息的对象,指针移动

数据结构当前成员信息的对象有两个属性:valuedone,value是当前成员的值,done是布尔型值,用于标识遍历是否结束

模拟next方法例子:

var it = makeIterator(['a', 'b']);

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

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

for ... of循环会自动去找被遍历数据结构的Iterator接口

for...of内部调用的是数据结构的Symbol.iterator方法

部分数据结构原生具有Iterator接口,如Array, Map, Set, String, TypedArray, 函数的arguements对象, NodeList对象,而有的数据结构没有原生的Iterator接口,如普通Object

但无论哪种数据结构,ES6规定数据结构默认的Iterator接口部署在数据结构的Symbol.iterator属性,调用Symbol.iterator方法会得到当前数据结构默认的遍历器生成函数

如果想要修改数据结构的默认Iterator接口:

const obj = { // obj是一个普通的Object
    ...
}
obj[Symbol.iterator] = function() {
        return {
            next: function() {
                return {
                    value: 1,
                    done: true
                };
            }
        }
    }

使用Iterator接口的场景:

  • 解构赋值
  • 扩展运算符 (任何部署了Iterator接口的数据结构都可以转换为数组)
  • yield*
yield* 后面可以跟一个可遍历结构

let generator = function* () {
    yield 1;
    yield* [2, 3, 4];
    yield 5;
};

var iterator = generator();

iterator.next();
iterator.next();
...
  • for...of, Array.from(), Map(), Set(), WeakMap(), WeakSet(), Promise.all(), Promise.race()

字符串的Iterator接口

var s = 'hi';

var iterator = s[Symbol.iterator](); // 遍历器对象

iterator.next();
iterator.next();
iterator.next();

Iterator接口与Generator函数

Generator函数是Symbol.iterator方法的最简单实现

var myIterable = {};

myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};

[...myIterable] // [1, 2, 3]

// 或下面的简洁写法:
let obj = {
    * [Symbol.iterator]() {
        yield 'hello';
        yield 'world';
    }
};

for (let x of obj) {
    console.log(x);
}

遍历器对象的return()和throw()

next()方法是必须的

next()和throw()方法是可选的

for...of中,如果循环提前退出(出错或break、continue语句)就会调用return()方法。常用于对象遍历完成前需要清理释放资源

throw()方法主要配合Generator函数使用,一般遍历器对象用不到

for...of对于普通的对象

不能直接使用

普通Object使用for...in会可以遍历key

如何遍历一个普通对象:

// 方法1
for (var key of Object.keys(obj)) {
    console.log(key, obj[key])
}

// 方法2: 使用Generator函数将对象重新包装
function* entries(obj) {
    for (let key of Object.keys(obj)) {
        yield [key, obj[key]];
    }
}

for (let [key, value] of entries(obj)) {
    console.log(key, value)
}

Generator(生成器)

Generator函数会返回一个遍历器对象(指针对象),由此可以理解Generator函数是一个遍历器对象生成函数,返回的遍历器对象可以依次遍历Generator函数内部的每一个状态

Generator函数有两个特征:

  1. function命令和函数名之间有一个*
  2. 函数体内部使用yield语句定义不同的内部状态
function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
};

var hw = helloWorldGenerator();

hw.next();
hw.next();
hw.next();

*yield语句逻辑在此不再赘述

Generator函数可以不用yield语句,就变成了一个暂缓执行的函数

function* f() {
    console.log('执行');
}

var gen = f(); // 如果f是一个普通函数,则在这一步就执行了

setTimeout(function () {
    gen.next()
}, 2000)

任意一个对象的Symbol.iterator方法等于该对象的遍历器对象生成函数

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};

[...myIterable] // [1, 2, 3]

next方法的参数

function* f () {
    for(var i = 0; true; i++) { // 无限运行的Generator函数
        var reset = yield i;
        if (reset) { i = -1 };
    }
}

var g = f();

g.next();
g.next();
...
g.next(true);

yield语句的返回值是undefined,reset的值也是undefined,next方法带有一个参数时,这个参数被当做已经执行完的上一条yield语句返回值(yield的返回值不是undefined了)

for...of可以自动遍历Generator函数生成的Iterator对象

function* foo() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
    return 6
};

for (let v of foo()) {
    console.log(v);
};

使用Generator函数和for...of循环实现斐波那契数列:

function* fibonacci () {
    let [prev, curr] = [0, 1];
    for (;;) {
        [prev, curr] = [curr, prev + curr];
        yield curr;
    }
}

for (let n of fibonacci()) {
    if (n > 1000) break;
    console.log(n);
}