FE-DSHUI / DSHUI

前端王者小分队读书会
4 stars 1 forks source link

《重学ES6:生成器Generator》——2021-03-18 #69

Open isbaselvy opened 3 years ago

isbaselvy commented 3 years ago
/**
 * 生成器基础
 * 生成器的形式是一个函数,函数名称前面加一个星号( *)表示它是一个生成器。只要是可以定义函数的地方,就可以定义生成器。
 * 注意 箭头函数不能用来定义生成器函数。
 * 标识生成器函数的星号不受两侧空格的影响:
 */
{
    // 生成器函数声明
    function* generatorFn() {
         return 'foo has return value'
    }

    // 调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行( suspended)的状态。
    const g = generatorFn()
    console.log(g)
    /*
        generatorFn {<suspended>}
        __proto__: Generator
        [[GeneratorLocation]]: 10_generator.js:9
        [[GeneratorState]]: "suspended"
        [[GeneratorFunction]]: ƒ* generatorFn()
        [[GeneratorReceiver]]: Window
        [[Scopes]]: Scopes[2]
    */
    // 与迭代器相似,生成器对象也实现了 Iterator 接口,因此具有 next()方法。调用这个方法会让生成器开始或恢复执行
    console.log(g.next); // ƒ next() { [native code] }
    // next()方法的返回值类似于迭代器,有一个 done 属性和一个 value 属性。
    // value 属性是生成器函数的返回值,默认值为 undefined,可以通过生成器函数的返回值指定
    console.log(g.next()) // {value: "foo has return value", done: true}

    // 生成器对象实现了 Iterable 接口,它们默认的迭代器是自引用的:
    console.log(g === g[Symbol.iterator]()); // true
}
/**
 * 通过yield中断执行
 * yield 关键字可以让生成器停止和开始执行,也是生成器最有用的地方。生成器函数在遇到 yield关键字之前会正常执行。
 * 遇到这个关键字后,执行会停止,函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用 next()方法来恢复执行:
 * 
 * 此时的 yield 关键字有点像函数的中间返回语句,它生成的值会出现在 next()方法返回的对象里。
 * 通过 yield 关键字退出的生成器函数会处在 done: false 状态;通过 return 关键字退出的生成器函数会处于 done: true 状态。
 * 
 * yield 关键字必须直接位于生成器函数定义中,出现在嵌套的非生成器函数中会抛出语法错误
 */
{
    function* gFn2() {
        for (let i = 0; ; ++i) {
            yield i;
        }
    }
    let g2 = gFn2();
    console.log(g2.next().value); // 0
    console.log(g2.next().value); // 1
    console.log(g2.next().value); // 2
    console.log(g2.next().value); // 3
}

/**
 * next传值与yield表达式
 */
{
    function* gen2() {
        let val
        val = yield 'abc'
        console.log('val-------------', val)
        let val2 = yield 'bcd'
        console.log('val2-------------', val2)
        // return val2
    }

    const l2 = gen2();
    console.log(l2.next(10)); // {value: "abc", done: false}
    // 遇到第二个yield暂停,第一个yield接收next传参20,计算并赋值给val,输出20并将‘bcd’返回
    console.log(l2.next(20)); // val------------- 20  {value: "bcd", done: false}
    // 执行第二个yield左边赋值 30,但此时并没有将yield表达式的值返回,所以是undefined 如果第二个加了return,next执行结果将是{value: 30, done: true}
    console.log(l2.next(30)); // val2------------- 30 {value: undefined, done: true}
}
/**
 * 3. 产生可迭代对象
 * 可以使用星号增强 yield 的行为,让它能够迭代一个可迭代对象(或generator对象),从而一次产出一个值
 * 与生成器函数的星号类似, yield 星号两侧的空格不影响其行为
 */
{
    function* generatorFn() {
        yield* [1, 2, 3];
    }
    for (const x of generatorFn()) {
        console.log(x); // 1 2 3
    }

   // 对于生成器函数产生的迭代器来说,这个值就是生成器函数返回的值:
    function* innerGeneratorFn() {
        yield 'foo';
        return 'bar';
    }
    function* outerGeneratorFn(genObj) {
        console.log('iter value:', yield* innerGeneratorFn());
    }
    for (const x of outerGeneratorFn()) {
        console.log('value:', x);
    }
    // value: foo
    // iter value: bar
}

/**
 * 提前终止生成器
 * 与迭代器类似,生成器也支持“可关闭”的概念。一个实现 Iterator 接口的对象一定有 next()方法,
 * 还有一个可选的 return()方法用于提前终止迭代器。生成器对象除了有这两个方法,还有第三个方法: throw()。
 * return()和 throw()方法都可以用于强制生成器进入关闭状态。
 */

{
    console.log('---------生成器中止return------------------');
    // 1. return()方法会强制生成器进入关闭状态。提供给 return()方法的值,就是终止迭代器对象的值:
    // 与迭代器不同,所有生成器对象都有 return()方法,只要通过它进入关闭状态,就无法恢复了。
    // 后续调用 next()会显示 done: true 状态,而提供的任何返回值都不会被存储或传播:
    function* generatorFn() {
        for (const x of [1, 2]) {
            yield x;
        }
    }

    const g = generatorFn();
    console.log('return前的生成器:', g); // generatorFn {<suspended>}
    console.log(g.next()); // { done: false, value: 1 }
    console.log(g.return(4)); // { done: true, value: 4 }
    console.log('retur后的生成器:', g); // generatorFn {<closed>}
    console.log(g.next()); // { done: true, value: undefined }
    console.log(g.next()); // { done: true, value: undefined }

    // for-of 循环等内置语言结构会忽略状态为 done: true 的 IteratorObject 内部返回的值。
    console.log('-------for...of..中的return------------')
    function* gen() {
        for (const x of [1, 2, 3]) {
            yield x;
        }
    }
    const l = gen();
    // for (const x of l) {
    //     if (x > 1) {
    //         g.return(4);
    //     }
    //     console.log(x); // 1 2 3 
    // }
}

/**
 * throw()方法会在暂停的时候将一个提供的错误注入到生成器对象中。如果错误未被处理,生成器就会关闭:
 * 不过,假如生成器函数内部处理了这个错误,那么生成器就不会关闭,而且还可以恢复执行。
 * 处理会跳过对应的 yield,因此在这个例子中会跳过一个值。
 */
{
    console.log('---------生成器中止throw------------------');
    // throw 内部不处理错误
    function* generatorFn() {
        for (const x of [1, 2, 3]) {
            yield x;
        }
    }
    const g = generatorFn();
    console.log(g, g.next()); // generatorFn {<suspended>} {value: 1, done: false}
    try {
        g.throw('foo');
    } catch (e) {
        console.log(e); // foo
    }
    console.log(g, g.next()); // generatorFn {<closed>} {value: undefined, done: true}

    console.log('------throw,迭代器内部处理了错误------------');
    function* generatorFn2() {
        for (const x of [1, 2, 3]) {
            try {
                yield x;
            } catch (e) { }
        }
    }

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