美文网首页ES6Web前端之路首页推荐
Generator(生成器)(一)

Generator(生成器)(一)

作者: 549b968de8fa | 来源:发表于2017-01-03 22:34 被阅读130次

    生成器,ECMAScript 6 的一个新特性,是可以暂停和恢复的函数(可以多任务进行协同工作)。这有助于许多应用程序:迭代器,异步编程等。本节介绍了生成器的工作原理,并给出了它们应用程序的概述。

    概述

    生成器有两个最主要的应用:

    • 实现迭代功能
    • 封装异步函数调用

    下面呢,给出两个示例。更具体的示例将在后面给出。
    (1)通过生成器实现可迭代性

    function* objectEntries(obj) {
        let propKeys = Reflect.ownKeys(obj);
        for (let propKey of propKeys) {
            yield [propKey, obj[propKey]];
        }
    } 
    
    let jane = { first: 'Jane', last: 'Doe' };
    for (let [key,value] of objectEntries(jane)) {
        console.log(`${key}: ${value}`);
    } 
    // first: Jane 
    // last: Doe 
    

    (2)封装异步函数调用

    co(function* () {
        try {
            let [croftStr, bondStr] = yield Promise.all([ 
                getFile('http://localhost:8000/croft.json'),
                getFile('http://localhost:8000/bond.json'),
            ]);
            let croftJson = JSON.parse(croftStr);
            let bondJson = JSON.parse(bondStr);
            console.log(croftJson);
            console.log(bondJson);
        } 
        catch (e) {
            console.log('Failure to read: ' + e);
        }
    }); 
    

    相信大家都是看不懂的。没关系,暂时呢,也不需要看懂它们。只是表达它的作用而已。


    什么是 Generator

    Generator 其实就是一个函数。只不过它拥有暂停和恢复的功能。

    function* name() {}
    console.log(name instanceof Function);  // true
    

    我们来创建一个 Generator 吧:

    function* genFunc() {
        console.log('First');
        yield; // A
        console.log('Second');  // B
    }
    

    它的形式就是在 function 的后面加一个星号(),而 yield 就是具有暂停功能的关键字。*

    如果我们直接调用这个函数,那么它是不会执行的。我们必须去控制它的执行。

    let genObj = genFunc();
    

    genFunc( ) 最初暂停在它的 body 的开始。方法 genObj.next( ) 继续执行 genFunc,直到下一个 yield 为止:

    genObj.next();  // "First"
    // { value: undefined, done: false }
    

    正如你在最后一行中看到的,genObj.next( ) 也返回一个对象。但让我们现在忽略它。一旦我们把生成器看作迭代器,这将是十分重要的。

    genFunc 现在在 A 行中暂停。如果我们再次调用 next( ) 方法,则继续执行并执行 B 行:

    genObj.next();  // "Second"
    // { value: undefined, done: true }
    

    然后,函数完成,执行已离开正文。如果进一步调用 genObj.next( ) ,则将不会有任何效果。

    根据以上的步骤,我们就详细的讲述了怎样创建一个简单的 Generator。


    Generator 的创建方式

    上一小节我们说到了 Generator 是怎么运行与工作的。这里我们将向大家讲讲它有哪些创建方式。
    (1)Generator 函数声明

    function* genFunc() { ··· }
    let genObj = genFunc();
    

    (2)Generator 函数表达式

    const genFunc = function* () { ··· };
    let genObj = genFunc();
    

    (3)Object 字面量的方法

    let obj = {
        * generatorMethod() {
            // ···
        }
    };
    let genObj = obj.generatorMethod();
    

    (4)Class 的方法

    class MyClass {
        * generatorMethod() {
           // ···
        }
    }
    let myInst = new MyClass();
    let genObj = myInst.generatorMethod();
    

    以上就是它的四种创建方式。很简单吧。

    那么 Generator 究竟扮演了什么样的角色呢?有以下三个角色:

    1. Iterators (数据生成者):我们使用 next( ) 方法通过 yield 返回值,这就意味着生成器可以通过循环和递归生成值的序列。所以生成器对象可以实现 Iterator 的接口,这些序列也被 ES6 的任何构造函数所支持。举两个例子:for-of 循环和展开操作符。
    1. Observers (数据消费者):yield 也能接收 next( ) 方法返回的值。这就意味着生成器变成了数据的消费者,生成器可以暂停,直到通过 next( ) 方法传递新的值进入生成器内。
    2. Coroutines (数据的生产者与消费者):考虑到生成器是可停止的并且可以是数据生成器和数据使用器,所以不需要将它们变成协同程序(协作多任务)。

    我们下几小节就来详细探讨扮演的角色。


    Generator 作为 Iterator (数据生成者)

    生成器函数通过 yield 生成一系列值,数据消费者通过迭代器方法 next( ) 消耗这些值。例如,以下生成器函数生成值 'a' 和 'b':

    function* genFunc() {
        yield 'a';
        yield 'b';
    } 
    let genObj = genFunc();
    genObj.next();  // { value: 'a', done: false } 
    genObj.next();  // { value: 'b', done: false } 
    genObj.next();  // { value: undefined, done: true }  
    

    大家看明白了吗?

    由于生成器对象是可迭代的,所以支持迭代的 ES6 语言结构可以应用于他们。 以下三个特别重要:
    (一)for-of 循环

    for (let x of genFunc()) {
         console.log(x);
    } 
    // "a"
    // "b"
    

    (二)展开操作符

    let arr = [...genFunc()];  // ['a', 'b'] 
    

    (三)解构

    let [x, y] = genFunc(); 
    console.log(x);  // "a"
    console.log(y);  // "b"
    

    好了,我们回到最开始的那个 next ( ) 方法的最后,返回的是 { value: undefined, done: true }。这显然没有问题,我们将这种返回值叫做“隐式返回”,那么当然就有“显式返回”啦。别忘了,生成器本身就是一个函数,因此我们可以使用 return 作为“显式返回”的结果。

    function* genFuncWithReturn() {
        yield 'a';
        yield 'b';
        return 'OK!';
    } 
    

    输出结果如下:

    let genObjWithReturn = genFuncWithReturn(); 
    genObjWithReturn.next();  // { value: 'a', done: false } 
    genObjWithReturn.next();;  // { value: 'b', done: false }  
    genObjWithReturn.next();;  // { value: 'OK!', done: true }  
    

    当然,这只是在函数中的情况,如果我们使用 for-of 循环或者展开操作符时,它将忽略我们所写的 return 返回值。这点需要注意一下。

    另外还有一点格外重要:yield 只可用于 Generator 中,不可用于回调函数中。

    以下例子很好的说明了问题。

    function* genFunc() {
        ['a', 'b'].forEach(x => yield x); // SyntaxError
    } 
    

    但是,却可以与迭代相关联。(这真是个微妙的关系啊)

    function* genFunc() {
        for (let x of ['a', 'b']) {
            yield x; // OK
        }
    } 
    

    通过 yield * 递归

    我们通过了前面的学习,知道了 yield 方法,是可以暂停以及输出我们的值。那么这个 yield * 是个什么东西呀?实际上,它算是一个与 super 类似的方法。下面我们通过一个例子来看看吧:

    function* foo() {
        yield 'a';
        yield 'b';
    } 
    

    我们定义了一个名为 foo 的生成器。接下来我们再定义一个 bar 生成器,使其迭代 foo 中的值:

    function* bar() {
        yield 'x';
        foo(); // 什么也没有发生?
        yield 'y';
    } 
    

    这时候,我们就要搬出 yield * 了!

    function* bar() {
        yield 'x';
        yield * foo();
        yield 'y';
    } 
    

    接下来就是见证奇迹的时刻了:

    let arr = [...bar()];
    // ['x', 'a', 'b', 'y'] 
    

    当然还有以下两种方法也同样可以实现:

    function* bar() {
        yield 'x';
        for (let value of foo()) {
            yield value;
        }
        yield 'y';
    } 
    

    function* bla() {
        yield 'sequence';
        yield* ['of', 'yielded'];
        yield 'values';
    }
    let arr = [...bla()];  // ['sequence', 'of', 'yielded', 'values'] 
    

    所以,具体的使用取决于你的爱好啦。


    总结

    本节的代码示例很多,所以需要大家认真的去阅读。由于大家的接收能力有限,不可能一下子学习那么多东西,所以下一节再接着讲剩下的两种扮演角色。

    相关文章

      网友评论

        本文标题:Generator(生成器)(一)

        本文链接:https://www.haomeiwen.com/subject/lehnvttx.html