生成器是ECMAScript 6新增的一个极为灵活的结构,拥有在一个函数块内暂停和恢复代码执行的能力。
    这种新能力具有深远的影响,比如,使用生成器可以自定义迭代器和实现协程。
    7.3.1 生成器基础
    生成器的形式是一个函数,函数名称前面加一个星号(*)表示它是一个生成器。
    只要是可以定义函数的地方,就可以定义生成器。
    image.png
    注:箭头函数不能用来定义生成器函数。
    标识生成器函数的星号不受两侧空格的影响
    调用生成器函数会产生一个生成器对象。
    生成器对象一开始处于暂停执行(suspended)的状态。
    与迭代器相似,生成器对象也实现了Iterator接口,因此具有next()方法。调用这个方法会让生成器开始或恢复执行。

    1. function * generatorFn() {}
    2. const g = generatorFn();
    3. console.log(g); // generatorFn {<suspended>}
    4. console.log(g.next); // ƒ next() { [native code] }

    next()方法的返回值类似于迭代器,有一个done属性和一个value属性。
    函数体为空的生成器函数中间不会停留,调用一次next()就会让生成器到达done: true状态。

    1. function * generatorFn() {}
    2. const generatorObject = generatorFn();
    3. console.log(generatorObject); // generatorFn {<suspended>}
    4. console.log(generatorObject.next()); // ƒ next() { [native code] }

    value属性是生成器函数的返回值,默认值为undefined,可以通过生成器函数的返回值指定:

    1. function * generatorFn() {
    2. return 'foo';
    3. }
    4. const generatorObject = generatorFn();
    5. console.log(generatorObject); // generatorFn {<suspended>}
    6. console.log(generatorObject.next()); // {value: "foo", done: true}

    生成器函数只会在初次调用next()方法后开始执行,如下所示:

    1. function * generatorFn() {
    2. console.log('foo');
    3. }
    4. let generatorObject = generatorFn(); // 初次调用生成器函数并不会打印日志
    5. generatorObject.next(); // foo

    生成器对象实现了Iterable接口,它们默认的迭代器是自引用的:

    1. function * generatorFn() {}
    2. console.log(generatorFn); // ƒ * generatorFn() {}
    3. console.log(generatorFn()[Symbol.iterator]); // ƒ [Symbol.iterator]() { [native code] }
    4. console.log(generatorFn()); // generatorFn {<suspended>}
    5. console.log(generatorFn()[Symbol.iterator]());// generatorFn {<suspended>}
    6. const g = generatorFn();
    7. console.log(g === g[Symbol.iterator]()); // true

    7.3.2 通过yield中断执行
    yield关键字可以让生成器停止和开始执行,也是生成器最有用的地方。
    生成器函数在遇到yield关键字之前会正常执行。遇到这个关键字后,执行会停止,函数作用域的状态会被保留。
    停止执行的生成器函数只能通过在生成器对象上调用next()方法来恢复执行:

    1. function * generatorFn() {
    2. yield;
    3. }
    4. let generatorObject = generatorFn();
    5. console.log(generatorObject.next()); // {value: undefined, done: false}
    6. console.log(generatorObject.next()); // {value: undefined, done: true}

    此时的yield关键字有点像函数的中间返回语句,它生成的值会出现在next()方法返回的对象里。
    通过yield关键字退出的生成器函数会处在done: false状态;
    通过return关键字退出的生成器函数会处于done: true状态。

    1. function * generatorFn() {
    2. yield 'foo';
    3. yield 'bar';
    4. return 'baz';
    5. }
    6. let generatorObject = generatorFn();
    7. console.log(generatorObject.next()); // {value: "foo", done: false}
    8. console.log(generatorObject.next()); // {value: "bar", done: false}
    9. console.log(generatorObject.next()); // {value: "baz", done: true}

    生成器函数内部的执行流程会针对每个生成器对象区分作用域。
    在一个生成器对象上调用next()不会影响其他生成器
    yield关键字只能在生成器函数内部使用,用在其他地方会抛出错误。
    类似函数的return关键字,yield关键字必须直接位于生成器函数定义中,出现在嵌套的非生成器函数中会抛出语法错误:
    image.png
    1.生成器对象作为可迭代对象
    在生成器对象上显式调用next()方法的用处并不大。
    其实,如果把生成器对象当成可迭代对象,那么使用起来会更方便:

    1. function * generatorFn() {
    2. yield 1;
    3. yield 2;
    4. yield 3;
    5. }
    6. for (const x of generatorFn()) {
    7. console.log(x);
    8. }
    9. // 1
    10. // 2
    11. // 3

    在需要自定义迭代对象时,这样使用生成器对象会特别有用。
    比如,我们需要定义一个可迭代对象,而它会产生一个迭代器,这个迭代器会执行指定的次数。使用生成器,可以通过一个简单的循环来实现:

    1. function * nTimes(n) {
    2. while (n--) {
    3. yield;
    4. }
    5. }
    6. for (let _ of nTimes(3)) {
    7. console.log('foo');
    8. }
    9. // 输出3次 foo

    传给生成器的函数可以控制迭代循环的次数。在n为0时,while条件为假,循环退出,生成器函数返回。
    2.使用yield实现输入和输出
    除了可以作为函数的中间返回语句使用,yield关键字还可以作为函数的中间参数使用。
    上一次让生成器函数暂停的yield关键字会接收到传给next()方法的第一个值。
    这里有个地方不太好理解——第一次调用next()传入的值不会被使用,因为这一次调用是为了开始执行生成器函数:

    1. function * generatorFn(initical) {
    2. console.log(initical);
    3. console.log(yield);
    4. console.log(yield);
    5. }
    6. let generatorObject = generatorFn('foo');
    7. generatorObject.next('bar'); // foo
    8. generatorObject.next('baz'); // baz
    9. generatorObject.next('quz'); // quz

    yield关键字可以同时用于输入和输出

    1. function * generatorFn(initical) {
    2. return yield 'foo';
    3. }
    4. let generatorObject = generatorFn();
    5. console.log(generatorObject.next()); // {value: "foo", done: false}
    6. console.log(generatorObject.next('baz')); // {value: "baz", done: true}

    因为函数必须对整个表达式求值才能确定要返回的值,所以它在遇到yield关键字时暂停执行并计算出要产生的值:”foo”。下一次调用next()传入了”bar”,作为交给同一个yield的值。然后这个值被确定为本次生成器函数要返回的值。
    yield关键字并非只能使用一次。
    比如,以下代码就定义了一个无穷计数生成器函数:

    1. function * generatorFn(initical) {
    2. for (let i = 0; ; ++i) {
    3. yield i;
    4. }
    5. }
    6. let generatorObject = generatorFn();
    7. console.log(generatorObject.next().value); // 0
    8. console.log(generatorObject.next().value); // 1

    3.产生可迭代对象
    可以使用星号增强yield的行为,让它能够迭代一个可迭代对象,从而一次产出一个值:

    1. // 等价的generatorFn
    2. // function * generatorFn() {
    3. // for (const x of [1,2,3]) {
    4. // yield x;
    5. // }
    6. // }
    7. function * generatorFn() {
    8. yield * [1,2,3]
    9. }
    10. let generatorObject = generatorFn();
    11. for (const x of generatorFn()) {
    12. console.log(x);
    13. }
    14. // 1
    15. // 2
    16. // 3

    与生成器函数的星号类似,yield星号两侧的空格不影响其行为
    因为yield*实际上只是将一个可迭代对象序列化为一连串可以单独产出的值,所以这跟把yield放到一个循环里没什么不同
    下面两个生成器函数的行为是等价的:

    1. function * generatorFnA() {
    2. for (const x of [1,2,3]) {
    3. yield x;
    4. }
    5. }
    6. for (const x of generatorFnA()) {
    7. console.log(x);
    8. }
    9. // 1
    10. // 2
    11. // 3
    12. function * generatorFnB() {
    13. yield * [1,2,3]
    14. }
    15. for (const x of generatorFnB()) {
    16. console.log(x);
    17. }
    18. // 1
    19. // 2
    20. // 3

    yield*的值是关联迭代器返回done: true时的value属性。
    对于普通迭代器来说,这个值是undefined:

    1. function * generatorFn() {
    2. console.log('iter value: ', yield * [1,2,3]); // iter value: undefined
    3. }
    4. for (const x of generatorFn()) {
    5. console.log('value: ', x);
    6. }
    7. // value: 1
    8. // value: 2
    9. // value: 3

    对于生成器函数产生的迭代器来说,这个值就是生成器函数返回的值:

    1. function * innerGeneratorFn() {
    2. yield 'foo';
    3. return 'bar';
    4. }
    5. function * outerGeneratorFn(genObj) {
    6. console.log('iter value: ', yield * innerGeneratorFn());
    7. }
    8. for (const x of outerGeneratorFn()) {
    9. console.log('value: ', x);
    10. }
    11. // value: foo
    12. // iter value: bar

    4.使用yield*实现递归算法
    yield*最有用的地方是实现递归操作,此时生成器可以产生自身。
    看下面的例子:

    1. function * nTimes(n) {
    2. if (n > 0) {
    3. yield * nTimes(n-1);
    4. yield n-1;
    5. }
    6. }
    7. for (const x of nTimes(3)) {
    8. console.log(x);
    9. }
    10. // 0
    11. // 1
    12. // 2

    在这个例子中,每个生成器首先都会从新创建的生成器对象产出每个值,然后再产出一个整数。
    结果就是生成器函数会递归地减少计数器值,并实例化另一个生成器对象。
    从最顶层来看,这就相当于创建一个可迭代对象并返回递增的整数。
    7.3.3 生成器作为默认迭代器
    因为生成器对象实现了Iterable接口,而且生成器函数和默认迭代器被调用之后都产生迭代器,所以生成器格外适合作为默认迭代器。
    下面是一个简单的例子,这个类的默认迭代器可以用一行代码产出类的内容:
    image.png
    for-of循环调用了默认迭代器(它恰好又是一个生成器函数)并产生了一个生成器对象。这个生成器对象是可迭代的,所以完全可以在迭代中使用。
    7.3.4 提前终止生成器
    与迭代器类似,生成器也支持“可关闭”的概念。
    next(), return(), throw()
    一个实现Iterator接口的对象一定有next()方法,还有一个可选的return()方法用于提前终止迭代器。生成器对象除了有这两个方法,还有第三个方法:throw()。
    return()和throw()方法都可以用于强制生成器进入关闭状态。
    1.return()
    return()方法会强制生成器进入关闭状态。
    提供给return()方法的值,就是终止迭代器对象的值:

    1. function * generatorFn(n) {
    2. for (const x of [1,2,3]) {
    3. yield x;
    4. }
    5. }
    6. const g = generatorFn();
    7. console.log(g);
    8. console.log(g.return(4));
    9. console.log(g);
    10. // generatorFn {<suspended>}
    11. // {value: 4, done: true}
    12. // generatorFn {<closed>}

    与迭代器不同,所有生成器对象都有return()方法,只要通过它进入关闭状态,就无法恢复了。
    后续调用next()会显示done: true状态,而提供的任何返回值都不会被存储或传播:

    1. function * generatorFn(n) {
    2. for (const x of [1,2,3]) {
    3. yield x;
    4. }
    5. }
    6. const g = generatorFn();
    7. console.log(g.next()); // {value: 1, done: false}
    8. console.log(g.return(4)); // {value: 4, done: true}
    9. console.log(g.next()); // {value: undefined, done: true}
    10. console.log(g.next()); // {value: undefined, done: true}
    11. console.log(g.next()); // {value: undefined, done: true}

    for-of循环等内置语言结构会忽略状态为done: true的IteratorObject内部返回的值。

    1. function * generatorFn(n) {
    2. for (const x of [1,2,3]) {
    3. yield x;
    4. }
    5. }
    6. const g = generatorFn();
    7. for (const x of g) {
    8. if (x > 1) {
    9. g.return(4);
    10. }
    11. console.log(x);
    12. }
    13. // 1
    14. // 2

    2.throw()
    throw()方法会在暂停的时候将一个提供的错误注入到生成器对象中。
    如果错误未被处理,生成器就会关闭:

    1. function * generatorFn(n) {
    2. for (const x of [1,2,3]) {
    3. yield x;
    4. }
    5. }
    6. const g = generatorFn();
    7. console.log(g); // generatorFn {<suspended>}
    8. try {
    9. g.throw('foo');
    10. } catch(e) {
    11. console.log(e); // foo
    12. }
    13. console.log(g); // generatorFn {<closed>}

    不过,假如生成器函数内部处理了这个错误,那么生成器就不会关闭,而且还可以恢复执行。
    错误处理会跳过对应的yield,因此在这个例子中会跳过一个值。
    比如:

    1. function * generatorFn(n) {
    2. for (const x of [1,2,3]) {
    3. try {
    4. yield x;
    5. } catch(e) {}
    6. }
    7. }
    8. const g = generatorFn();
    9. console.log(g.next()); // {value: 1, done: false}
    10. g.throw('foo');
    11. console.log(g.next()); // {value: 3, done: false}

    在这个例子中,生成器在try/catch块中的yield关键字处暂停执行。在暂停期间,throw()方法向生成器对象内部注入了一个错误:字符串”foo”。这个错误会被yield关键字抛出。因为错误是在生成器的try/catch块中抛出的,所以仍然在生成器内部被捕获。可是,由于yield抛出了那个错误,生成器就不会再产出值2。此时,生成器函数继续执行,在下一次迭代再次遇到yield关键字时产出了值3。
    注:如果生成器对象还没有开始执行,那么调用throw()抛出的错误不会在函数内部被捕获,因为这相当于在函数块外部抛出了错误。

    思考:
    forEach是否能跳出循环,如果能,请问可以以什么方法跳出?
    答:能
    通过抛出异常的方式终止循环

    1. try {
    2. let arr = [1,2,3,4,5,6];
    3. arr.forEach( function(item, index) {
    4. if (item === 4) {
    5. throw new Error('end interval');
    6. }
    7. console.log(item);
    8. })
    9. } catch(e) {
    10. if (e.message != 'end interval') throw e;
    11. }
    12. console.log('oooooover');
    13. // 1
    14. // 2
    15. // 3
    16. // oooooover

    排除的有:break, return false
    首先: break

    1. let arr = [1,2,3,4,5,6];
    2. arr.forEach( function(item, index){
    3. if (item === 4) {
    4. break;
    5. }
    6. console.log(item);
    7. })
    8. // Uncaught SyntaxError: Illegal break statement
    9. // 报错

    其次: return false

    1. let arr = [1,2,3,4,5,6];
    2. arr.forEach( function(item, index){
    3. if (item === 4) {
    4. return false;
    5. }
    6. console.log(item);
    7. })
    8. // 1
    9. // 2
    10. // 3
    11. // 5
    12. // 6

    return fasle 只是中止本次继续执行,而不是终止循环