Open isbaselvy opened 3 years ago
/** * 生成器基础 * 生成器的形式是一个函数,函数名称前面加一个星号( *)表示它是一个生成器。只要是可以定义函数的地方,就可以定义生成器。 * 注意 箭头函数不能用来定义生成器函数。 * 标识生成器函数的星号不受两侧空格的影响: */ { // 生成器函数声明 function* generatorFn() { return 'foo has return value' } // 调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行( suspended)的状态。 const g = generatorFn() console.log(g) /* generatorFn {<suspended>} __proto__: Generator [[GeneratorLocation]]: 10_generator.js:9 [[GeneratorState]]: "suspended" [[GeneratorFunction]]: ƒ* generatorFn() [[GeneratorReceiver]]: Window [[Scopes]]: Scopes[2] */ // 与迭代器相似,生成器对象也实现了 Iterator 接口,因此具有 next()方法。调用这个方法会让生成器开始或恢复执行 console.log(g.next); // ƒ next() { [native code] } // next()方法的返回值类似于迭代器,有一个 done 属性和一个 value 属性。 // value 属性是生成器函数的返回值,默认值为 undefined,可以通过生成器函数的返回值指定 console.log(g.next()) // {value: "foo has return value", done: true} // 生成器对象实现了 Iterable 接口,它们默认的迭代器是自引用的: console.log(g === g[Symbol.iterator]()); // true } /** * 通过yield中断执行 * yield 关键字可以让生成器停止和开始执行,也是生成器最有用的地方。生成器函数在遇到 yield关键字之前会正常执行。 * 遇到这个关键字后,执行会停止,函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用 next()方法来恢复执行: * * 此时的 yield 关键字有点像函数的中间返回语句,它生成的值会出现在 next()方法返回的对象里。 * 通过 yield 关键字退出的生成器函数会处在 done: false 状态;通过 return 关键字退出的生成器函数会处于 done: true 状态。 * * yield 关键字必须直接位于生成器函数定义中,出现在嵌套的非生成器函数中会抛出语法错误 */ { function* gFn2() { for (let i = 0; ; ++i) { yield i; } } let g2 = gFn2(); console.log(g2.next().value); // 0 console.log(g2.next().value); // 1 console.log(g2.next().value); // 2 console.log(g2.next().value); // 3 } /** * next传值与yield表达式 */ { function* gen2() { let val val = yield 'abc' console.log('val-------------', val) let val2 = yield 'bcd' console.log('val2-------------', val2) // return val2 } const l2 = gen2(); console.log(l2.next(10)); // {value: "abc", done: false} // 遇到第二个yield暂停,第一个yield接收next传参20,计算并赋值给val,输出20并将‘bcd’返回 console.log(l2.next(20)); // val------------- 20 {value: "bcd", done: false} // 执行第二个yield左边赋值 30,但此时并没有将yield表达式的值返回,所以是undefined 如果第二个加了return,next执行结果将是{value: 30, done: true} console.log(l2.next(30)); // val2------------- 30 {value: undefined, done: true} } /** * 3. 产生可迭代对象 * 可以使用星号增强 yield 的行为,让它能够迭代一个可迭代对象(或generator对象),从而一次产出一个值 * 与生成器函数的星号类似, yield 星号两侧的空格不影响其行为 */ { function* generatorFn() { yield* [1, 2, 3]; } for (const x of generatorFn()) { console.log(x); // 1 2 3 } // 对于生成器函数产生的迭代器来说,这个值就是生成器函数返回的值: function* innerGeneratorFn() { yield 'foo'; return 'bar'; } function* outerGeneratorFn(genObj) { console.log('iter value:', yield* innerGeneratorFn()); } for (const x of outerGeneratorFn()) { console.log('value:', x); } // value: foo // iter value: bar } /** * 提前终止生成器 * 与迭代器类似,生成器也支持“可关闭”的概念。一个实现 Iterator 接口的对象一定有 next()方法, * 还有一个可选的 return()方法用于提前终止迭代器。生成器对象除了有这两个方法,还有第三个方法: throw()。 * return()和 throw()方法都可以用于强制生成器进入关闭状态。 */ { console.log('---------生成器中止return------------------'); // 1. return()方法会强制生成器进入关闭状态。提供给 return()方法的值,就是终止迭代器对象的值: // 与迭代器不同,所有生成器对象都有 return()方法,只要通过它进入关闭状态,就无法恢复了。 // 后续调用 next()会显示 done: true 状态,而提供的任何返回值都不会被存储或传播: function* generatorFn() { for (const x of [1, 2]) { yield x; } } const g = generatorFn(); console.log('return前的生成器:', g); // generatorFn {<suspended>} console.log(g.next()); // { done: false, value: 1 } console.log(g.return(4)); // { done: true, value: 4 } console.log('retur后的生成器:', g); // generatorFn {<closed>} console.log(g.next()); // { done: true, value: undefined } console.log(g.next()); // { done: true, value: undefined } // for-of 循环等内置语言结构会忽略状态为 done: true 的 IteratorObject 内部返回的值。 console.log('-------for...of..中的return------------') function* gen() { for (const x of [1, 2, 3]) { yield x; } } const l = gen(); // for (const x of l) { // if (x > 1) { // g.return(4); // } // console.log(x); // 1 2 3 // } } /** * throw()方法会在暂停的时候将一个提供的错误注入到生成器对象中。如果错误未被处理,生成器就会关闭: * 不过,假如生成器函数内部处理了这个错误,那么生成器就不会关闭,而且还可以恢复执行。 * 处理会跳过对应的 yield,因此在这个例子中会跳过一个值。 */ { console.log('---------生成器中止throw------------------'); // throw 内部不处理错误 function* generatorFn() { for (const x of [1, 2, 3]) { yield x; } } const g = generatorFn(); console.log(g, g.next()); // generatorFn {<suspended>} {value: 1, done: false} try { g.throw('foo'); } catch (e) { console.log(e); // foo } console.log(g, g.next()); // generatorFn {<closed>} {value: undefined, done: true} console.log('------throw,迭代器内部处理了错误------------'); function* generatorFn2() { for (const x of [1, 2, 3]) { try { yield x; } catch (e) { } } } const l = generatorFn2(); // 注意 如果生成器对象还没有开始执行,那么调用 throw()抛出的错误不会在函数内部被捕获,因为这相当于在函数块外部抛出了错误。 // l.throw('start') console.log(l, l.next()); // generatorFn2 {<suspended>} { done: false, value: 1} l.throw('foo'); console.log(l, l.next()); // generatorFn2 {<suspended>} { done: false, value: 3} // 在这个例子中,生成器在 try/catch 块中的 yield 关键字处暂停执行。在暂停期间, throw()方 // 法向生成器对象内部注入了一个错误:字符串"foo"。这个错误会被 yield 关键字抛出。因为错误是在 // 生成器的 try/catch 块中抛出的,所以仍然在生成器内部被捕获。可是,由于 yield 抛出了那个错误, // 生成器就不会再产出值 2。此时,生成器函数继续执行,在下一次迭代再次遇到 yield 关键字时产出了值 3。 }