关于 生成器 的一些笔记

318 阅读5分钟

生成器笔记

什么是生成器?

生成器是 ECMAScript 6 新增的一个极为灵活的结构,拥有在一个函数块内暂停和恢复代码执行的能力。

生成器函数的声明

/** 生成器的形式是一个函数,函数名称前面加一个星号(*)表示它是一个生成器。只要是可以定义函数的地方,就可以定义生成器。 */

// 函数声明
function* show() { }

// 函数表达式
let show = function* () { }

// 对象中的函数声明
let person = {
    *show() { }
}

// 类中的函数声明
class Person {
    *show() { }
}

// 作为静态类中的函数声明
class Person {
    static *show() { }
}

// 注意:箭头函数不能用来定义生成器函数

生成器函数的基本使用

// 调用生成器函数会产生一个生成器对象。
// 生成器对象一开始是处于暂停执行的状态。
// 生成器对象实现了 Iterator 接口,因此具有 next() 方法。
// 调用 next() 方法可以让生成器开始或恢复执行。


// next() 方法的返回值类似于迭代器,有一个 done 属性和一个 value 属性。函数体为空的生成器函数中间不会停留,调用一次 next() 就会让生成器到达 done: true 状态。
function* eat() { }
const generator = eat();
console.log(generator.next());	//{ value: undefined, done: true }


// value 属性是生成器函数的返回值,默认值是 undefined,可以通过生成器函数的返回值指定
function* hello() {
	return 'Hello Generator !!!';
}
const generator = hello();
console.log(generator.next());	// { valeue: "Hello Generator !!!", done: true }

// 注意点:
// 生成器函数只会在初次调用 next() 方法后开始执行
// 生成器对象实现了 Iterable 接口,它们默认的迭代器是自引用的

通过 yield 中断执行

  • yield 关键字可以让生成器停止和开始执行,也是生成器最有用的地方。
  • 生成器函数在遇到 yield 关键字之前会正常执行。
  • 一旦遇到 yield 关键字,那么就会执行停止,函数的作用域的状态会被保留。
  • 停止执行的生成器函数只能通过生成器对象上调用 next() 方法来恢复执行。
// 此时的 yield 关键字有点像函数的中间返回语句,它生成的值会出现在 next() 方法返回的对象里。
// 通过 yield 关机子退出的生成器函数会处在 done: false 状态。
// 通过 return 关键字退出生成器函数会处于 done: true 状态。

function* eat() {
    yield "早晨干饭!";
    yield "中午干饭!";
    return '休息了!';
    yield "晚上干饭!";
}
const generator = eat();

console.log(generator.next());	// {value: "早晨干饭!", done: false }
console.log(generator.next());	// {value: "中午干饭!", done: false }
console.log(generator.next());	// {value: "休息了!", done: true }
console.log(generator.next());	// {value: undefined, done: true }

// 注意:
// 生成器函数内容部的执行流程会针对每个生成器对象区分作用域。
// 在一个生成器对象上调用 next() 不会影响其他生成器。
// yield 关键字只能在生成器函数内部使用,用在其他地方会抛出错误。
// 如果出现嵌套的非生成器函数中那么是无效的,比如:
function* hello() {
    function item() {
        yield;
    }
}
const generator = hello();
generator.next();
// 上面这个是无效的

生成器对象可以作为迭代对象

// 因为生成器对象实现了 Iterable 接口,而且生成器函数和默认迭代器被调用之后都产生迭代器,所以生成器格外合适作为默认迭代器。

// 示例:
function* hello() {
    yield '早晨';
    yield '中午';
    yield '晚上';
}
for (let value of hello()) {
    console.log(value);
}
// 打印结果:
// 早晨
// 中午
// 晚上

使用 yield 实现输入和输出

// 除了可以作为函数的中间返回语句使用,yield 关键字还可以作为函数的中间参数使用。
// 这里第一次调用 next() 传入的值不会被使用,因为这一次调用是为了开始执行生成器函数

function* hello(value) {
    console.log(value);
    console.log(yield);
                console.log(yield);
    console.log(yield);
                }
const generator = hello('清晨');
generator.next('这里的值不会被使用');
generator.next('中午');
generator.next('晚上');


// yield 关键字可以同时用于输入和输出
function* hello(value) {
    return yield 'hello';
}
const generator = hello();
console.log(generator.next());				// { value: "hello", done: false }
console.log(generator.next('Generator'));	// { value: "Generator", done: true }

// 因为函数必须对整个表达式求值才能确定要返回的值,
// 所以它在遇到 yield 关键字时暂停执行并计算出要产生的值
// 下一次调用 next() 传入了一个值,作为交给同一个 yield 的值。
// 然后这个值被确定为本次生成器函数要返回的值

产生可迭代对象

// 可以使用星号增强 yield 的行为,让它能够迭代一个可迭代对象,从而一次产出一个值

function* generatorFunc(value) {
    yield* [1, 2, 3];
}
const generator = generatorFunc();
console.log(generator.next());	// {value: 1, done: false}
console.log(generator.next());	// {value: 2, done: false}
console.log(generator.next());	// {value: 3, done: false}

// 因为 yield* 实际上只是将可迭代对象序列化为一连串可以单独产出的值,所以这跟把 yield 放到一个循环里没什么不同。

提前终止生成器

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

// return() 和 throw() 方法都可以用于强制生成器进入关闭状态。

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

2. throw()
// throw()方法会在暂停的时候将一个提供的错误注入到生成器对象中。如果错误未被处理,生成器会关闭
function* generatorFunc(value) {
    yield 1;
    try {
        throw new Error('出错了');
    } catch (err) {
        console.log(err);
    }
    yield 2;
}
const generator = generatorFunc();
console.log(generator.next());
console.log(generator.next());
// 不过,假如生成器函数内部处理了这个错误,那么生成器就不会关闭,而且还可以恢复执行。
// 错误处理会跳过对应的 yield,因此在这个例子中会跳过一个值。