ES6之生成器

63 阅读3分钟

ES6之生成器

前面我们讲了迭代器和可迭代对象,生成器又是什么?生成器是集合了迭代器和可迭代对象的一个对象。在它内部可以找到 next 方法和 Symbol.iterator 。所以它一定是用来迭代的。
实际上,生成器内部是由构造函数 Generator 创建的对象,但我们不能使用这个构造函数。我们只可以通过生成器函数创建生成器。它的形式比较特殊,必须在 function 后或者函数名前加上一个特殊符号 * 。之后这个函数的运行规则就发生了改变。例如:

function* test() {
    console.log("第1次运行")
    yield 1;
    console.log("第2次运行")
    yield 2;
    return 10;
    console.log("第3次运行")
    // return 10;
}
const generator = test();

test 函数就是一个生成器函数,这个函数一定会返回一个生成器,所以我们可以通过调用这个函数拿到一个生成器。然后就可以使用 next 方法调用。
注意到这里还有一个关键字 yield 。在每次调用生成器的 next 方法时,都会运行相关的代码,遇到 yield 关键字就会停下来,将 yield 里的值赋值给返回对象里的 value 值。继续调用 next 方法,就会停到下一个 yield 关键字处。当运行到最后的代码处,没有 yield 关键字时,这个函数迭代结束,返回对象里的 done 就会设置为 true 。
如果我们不使用 next 方法,只是调用函数是不会输出任何结果。因为调用函数只是创建了一个生成器,并不会运行这个函数,这是生成器函数和普通函数的区别。

function* createFeiboIterator() {
    let prev1 = 1,
        prev2 = 1, //当前位置的前1位和前2位
        n = 1;//当前的位置
    while (true) {
        if (n <= 2) {
            yield 1;
        } else {
            const newValue = prev1 + prev2
            yield newValue;
            prev2 = prev1;
            prev1 = newValue;
        }
        n++;
    }
}
const iterator = createFeiboIterator();

这是对斐波那契数列的改进。可以看到,不用再返回一个 next 对象。

function *test2() {
    let info = yield 1;//第二次调用next,传入参数5
    console.log(info)//5
    info = yield 2 + info;//第二次调用next之后,value为7
    console.log(info)
}
const generator2 = test2();

我们看到 yield 关键字前面有一个赋值的结果。它是为了当我们在第一次调用之后,第二次调用需要额外的一些信息。于是我们可以在第一次调用的时候在 next 方法传入一些参数。这个参数会变为 yield 表达式的值传递给变量。第二次调用的时候,就会改变 value 值。如果第一次调用传参,参数没有前面的 yield 表达式来接收,没有意义。
yield 关键字是在生成器函数内部使用,表示的是产生一个迭代数据,也就是产生一个 value 值。

function* test() {
    console.log("第1次运行")
    yield 1;
    console.log("第2次运行")
    yield 2;
    return 10;
    console.log("第3次运行")
    // return 10;
}
const generator = test();

如果函数有返回值的话,因为返回值代表的是函数结束,也就意味着迭代结束。所以 return 的值就会作为迭代结束的 value 值。只能是第一次结束的值,后面继续调用 next ,value 值会成为 undefined 。可以提前使用 return 来控制函数的迭代过程。

function* test3() {
    yield* test2();
    yield 3;
    yield 4;
}
const generator3 = test3();

也可以在生成器函数内部传入一个生成器函数,内部的函数必须加上 * 号才能正确调用。相当于将 test2 的内容复制到了 test3 的地方。
同时,还有两个 API 可以使用。一个是 return() 方法可以提前结束迭代,传入的参数就是最后的 value 值。另一个是 throw() 方法可以在下一次迭代时在之前的位置抛出错误。