Generator(生成器)(一)

生成器,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(生成器)(一))