Generator
随着现代Web应用程序的复杂性不断增加,JavaScript作为一门动态且灵活的语言也在不断演进。在这个演进的过程中,ECMAScript 2015(通常称为ES6)为JavaScript引入了许多令人兴奋的新特性,其中之一就是Generator(生成器)。
JavaScript中的迭代器是一种强大的概念,它使得处理序列数据变得更加简单和灵活。Generator不仅仅是迭代器的一种实现,它引入了一种新的编程范式,允许我们以更直观和异步的方式编写代码。
Generator函数
要创建一个Generator函数,我们使用 function* 来声明。
function* myGenerator() {
// 函数体
}yield关键字
在Generator函数内部,我们可以使用 yield 关键字,它的作用是将执行权暂时交还给调用者,并产生一个值。
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}上述例子中,每次调用Generator函数,它都会在 yield 处暂停,产生一个值,直到下一次调用才会继续执行。
Generator.prototype.next()
生成器函数是分段执行的,每次调用 next() 方法,函数就执行一步,到下一个 yield 或 return,因此我们可以理解为 yield 是函数暂停,return 是函数完成。
每一次调用 next() 其返回值为:
- value:返回值,
yield或return关键字右侧的返回值。 - done:生成器的状态,当结果为
true时,标志着生成器函数已经执行完成,如果继续调用返回的 value 为undefined。
调用Generator函数并不会立即执行它的代码,而是返回一个迭代器对象。我们可以使用这个对象来控制Generator函数的执行。
const generator = myGenerator();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }通过 next() 方法,我们可以逐步执行Generator函数的代码,并观察每次 yield 的返回值。
传递参数
Generator函数可以接受参数,这使得我们能够动态地影响生成器的行为。
function* generatorWithParams(param) {
yield param * 2;
yield param * 3;
}
const generator = generatorWithParams(5);
console.log(generator.next()); // { value: 10, done: false }
console.log(generator.next()); // { value: 15, done: false }再看一个
function* gen() {
const x = yield 1; // 此时暂停,返回 { value: 1, done: false }
console.log(x); // 在下一次调用 next() 时,x 被赋值为传入的参数,即 undefined
}
const g = gen();
console.log(g.next()); // 打印结果: { value: 1, done: false }
console.log(g.next()); // 打印结果: undefined,因为 x 被赋值为传入的参数,即 undefined解释:
yield 表达式本身没有返回值(undefined)。next() 可以带一个参数,该参数就会被当作上一个 yield 表达式的返回值。
function* gen() {
const x = yield 1;
console.log(x);
return 4;
}
const g = gen();
console.log(g.next(2));
// { value: 1, done: false }
console.log(g.next(3));
// x: 3 调用时传递的值
// { value: 4, done: false } return 返回的值for...of
值得注意的是,for...of 在执行过程中不会执行 return 语句
function* gen() {
yield 1;
yield 2;
return 3;
}
for (const g of gen()) {
console.log(g);
}
// 1 2练习题
function* generatorFn(i) {
console.log('i: ', i); // 打印结果: i: 10
const j = 5 * (yield i * 10);
console.log('j: ', j); // 打印结果: j: 50
const k = yield (2 * j) / 4;
console.log('k: ', k); // 打印结果: k: 5
return i + j + k;
}
const gen = generatorFn(10);
console.log(gen.next(20)); // 打印结果: { value: 100, done: false }
console.log(gen.next(10)); // 打印结果: { value: 25, done: false }
console.log(gen.next(5)); // 打印结果: { value: 65, done: true }解释
gen.next(20)开始执行生成器,打印 'i: 10',然后执行yield i * 10,返回{ value: 100, done: false }。注意:第一次调用next()时传入的参数会被忽略。此时生成器暂停在const j = 5 * (yield i * 10);处,等待下一次next()调用。gen.next(10)继续执行生成器,调用next(10)时,传入的参数10会作为上一个yield i * 10表达式的返回值,所以const j = 5 * 10 = 50,打印 'j: 50',然后执行yield (2 * j) / 4,即yield (2 * 50) / 4 = yield 25,返回{ value: 25, done: false }。此时生成器暂停在const k = yield (2 * j) / 4;处。gen.next(5)继续执行生成器,调用next(5)时,传入的参数5会作为上一个yield (2 * j) / 4表达式的返回值,所以const k = 5,打印 'k: 5',然后执行到函数末尾,返回{ value: 65, done: true }。生成器执行完毕,最后的返回值是i + j + k,即10 + 50 + 5 = 65。
Generator.prototype.throw()
与 next() 相似,Generator 生成的实例可以通过调用 throw() 方法来抛出错误。
function* gen() {
try {
yield 1;
yield 2;
yield 3;
} catch (e) {
console.log('捕获错误: ', e.message);
}
}
const g = gen();
console.log(g.next());
// { value: 1, done: false }
console.log(g.throw(new Error('出错了')));
// 捕获错误: 出错了
// { value: undefined, done: true }
console.log(g.next());
// { value: undefined, done: true }Generator.prototype.return()
同样的,还有个 return 方法,用来提前结束迭代。
function* gen() {
yield 1;
yield 2;
yield 3;
}
const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.return(4)); // { value: 4, done: true }
console.log(g.next()); // { value: undefined, done: true }yield*
在生成器函数中使用yield*时,会委托另一个生成器函数。
function* inner() {
yield 4;
yield 5;
}
function* outer() {
yield 1;
yield 2;
yield* inner();
yield 3;
}
for (const v of outer()) {
console.log(v);
}
// 1 2 4 5 3