生成器,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 究竟扮演了什么样的角色呢?有以下三个角色:
- Iterators (数据生成者):我们使用 next( ) 方法通过 yield 返回值,这就意味着生成器可以通过循环和递归生成值的序列。所以生成器对象可以实现 Iterator 的接口,这些序列也被 ES6 的任何构造函数所支持。举两个例子:for-of 循环和展开操作符。
- Observers (数据消费者):yield 也能接收 next( ) 方法返回的值。这就意味着生成器变成了数据的消费者,生成器可以暂停,直到通过 next( ) 方法传递新的值进入生成器内。
- 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']
所以,具体的使用取决于你的爱好啦。
总结
本节的代码示例很多,所以需要大家认真的去阅读。由于大家的接收能力有限,不可能一下子学习那么多东西,所以下一节再接着讲剩下的两种扮演角色。
网友评论