Open jannahuang opened 2 years ago
生成器(generator)函数能生成一组值的序列,但每个值的生成是基于每次请求。我们必须显式地向生成器请求一个新的值,随后生成器要么响应一个新生成的值,要么就告诉我们它之后都不会再生成新值。当对另一个值的请求到来后,生成器就会从上次离开的位置恢复执行。 普通函数只会返回一个单一值(或者不返回任何值)。而 generator 可以按需一个接一个地返回(“yield”)多个值。
创建一个生成器函数的语法:在关键字 function 后面加上一个星号(*)。生成器函数体内就能够使用新关键字 yield,从而生成独立的值。
function* generateSequence() { yield 1; yield 2; return 3; } // 定义一个生成器,它能生成一个包含3个数据的序列 let generator = generateSequence(); // 调用生成器得到一个迭代器
调用生成器并不会执行生成器函数,而是会创建一个叫作迭代器(iterator) 的对象。 迭代器用于控制生成器的执行。迭代器对象暴露的最基本接口是 next() 方法,用来向生成器请求一个值,从而控制生成器。 当 next() 方法被调用时,它会恢复生成器的运行,执行直到最近的 yield 语句。然后函数执行暂停,并将产出的(yielded)值返回到外部代码。 next() 的结果始终是一个具有两个属性的对象:
function* generateSequence() { yield 1; yield 2; return 3; } let generator = generateSequence();
let one = generator.next(); console.log(JSON.stringify(one)); // {value: 1, done: false}
// 再次调用 generator.next(),代码恢复执行并返回下一个 yield 的值 let two = generator.next(); console.log(JSON.stringify(two)); // {value: 2, done: false}
// 第三次调用 generator.next(),代码将会执行到 return 语句 let three = generator.next(); console.log(JSON.stringify(three)); // {value: 3, done: true}, generator 执行完成
每当生成一个当前值后,生成器就会非阻塞地挂起执行,随后耐心等待下一次值请求的到达。 如果生成器中没有 return,则在最后一个 yield 调用之后,再次调用会返回 {value: undefined, done: true。 > function* f(…) 或 function *f(…) 两种写法都可以,通常更倾向于第一种语法,因为星号 * 表示它是一个 generator 函数,它描述的是函数种类而不是名称。 ## 对迭代器进行迭代 通过调用生成器得到的迭代器,暴露出一个next方法能让我们向生成器请求一个新值。 试着用普通的while循环来迭代生成器生成的值序列: ```javascript function* WeaponGenerator(){ yield "Katana"; yield "Wakizashi"; } const weaponsIterator = WeaponGenerator(); // 新建一个迭代器 let item; // 创建一个变量,保存生成器产生的值 while(!(item = weaponsIterator.next()).done) { console.log(item !== null, item.value); } // 每次循环都会从生成器中取出一个值,然后输出该值。 // 当生成器不会再生成值的时候,停止迭代
这就是 for-of 循环的原理。for-of 循环不过是对迭代器进行迭代的语法糖。
for(var item of WeaponGenerator ()){ console.log(item !== null, item); }
正如在标准函数中调用另一个标准函数,我们可以把生成器的执行委托给另一个生成器。
function* WarriorGenerator(){ yield "Sun Tzu"; yield* NinjaGenerator(); // yield* 将执行权交给了另一个生成器 yield "Genghis Khan"; } function* NinjaGenerator(){ yield "Hattori"; yield "Yoshi"; } for(let warrior of WarriorGenerator()){ console.log(warrior !== null, warrior); } // 输出:Sun Tzu、Hattori、Yoshi、Genghis Khan
在迭代器上使用 yield* 操作符,程序会跳转到另外一个生成器上执行。 for-of 循环不会关心是否委托到另一个生成器上,它只关心在 done 状态到来之前都一直调用 next() 方法。
function* NinjaGenerator(action) { // 生成器可以像其他函数一样接收标准 参数 const imposter = yield ("Hattori " + action); // 生成器会返回一个中间计算结果。而在调用迭代器的 next() 方法传值时,可以将该值传递回生成器 console.log(imposter === "Hanzo", "The generator has been infiltrated"); yield ("Yoshi (" + imposter + ") " + action); // 传递回的值将成为yi eld表达式的返回值,因此impostrer的值是Hanzo } const ninjaIterator = NinjaGenerator("skulk"); // 普通的参数传递
const result1 = ninjaIterator.next(); console.log(result1.value === "Hattori skulk","Hattori is skulking"); // 触发生成器的执行,并检测返回值是否正确
const result2 = ninjaIterator.next("Hanzo"); // next() 方法传值时,imposter 结果即为该值 console.log(result2.value === "Yoshi (Hanzo) skulk", "We have an imposter!"); // 将数据作为next方法的参数传递给生成 器,并检测返回值是否符合预期
![双向通信](https://raw.githubusercontent.com/jannahuang/blog/main/pictures/generator-two-way.png) 另一个例子: ```javascript function* gen() { let ask1 = yield "2 + 2 = ?"; console.log(ask1); // 4 let ask2 = yield "3 * 3 = ?" console.log(ask2); // 9 } let generator = gen(); console.log( generator.next().value ); // "2 + 2 = ?" console.log( generator.next(4).value ); // "3 * 3 = ?" console.log( generator.next(9).done ); // true
个迭代器除了有一个 next() 方法,还有一个 throw() 方法。
function* NinjaGenerator() { try{ yield "Hattori"; fail("The expected exception didn't occur"); // 此处的错误将不会发生 } catch(e) { console.log(e === "Catch this!", "Aha! We caught an exception"); // 捕获异常并检测接收到的异常是否符合预期 } } const ninjaIterator = NinjaGenerator(); const result1 = ninjaIterator.next(); assert(result1.value === "Hattori", "We got Hattori"); // 从生成器取一个值 ninjaIterator.throw("Catch this!"); // 向生成器抛出一个异常
用生成器生成ID序列
function* IdGenerator() { // 定义生成器函数IdGenerator let id = 0; // 一个始终记录ID的变量,这个变量无法在生成器外部改变 while (true) { yield ++id; } // 循环生成无限长度的ID序列 } const idIterator = IdGenerator(); // 这个迭代器我们能够向生成器请求新的 ID 值 const ninja1 = { id: idIterator.next().value }; const ninja2 = { id: idIterator.next().value }; const ninja3 = { id: idIterator.next().value }; //请求3个新ID值 assert(ninja1.id === 1, "First ninja has id 1"); assert(ninja2.id === 2, "Second ninja has id 2"); assert(ninja3.id === 3, "Third ninja has id 3"); // 测试运行结果
局部变量 id 仅能在该生成器中被访问,不必担心会不小心在代码的其他地方修改 id 值。随后是一个无限的 while 循环,其每次迭代都能生成一个新 id 值并挂起执行,直到下一次ID请求到达。
调用一个生成器不会实际执行它,而是创建了一个新的迭代器,通过该迭代器才能从生成器中请求值。在生成器生成了一个值后,生成器会挂起执行并等待下一个请求的到来。在某种方面来说,生成器的工作更像是一个在状态中运动的状态机。
以上笔记参考《现代 JavaScript 教程》,《JavaScript 忍者秘籍(第2版)》
生成器 Generator
生成器(generator)函数能生成一组值的序列,但每个值的生成是基于每次请求。我们必须显式地向生成器请求一个新的值,随后生成器要么响应一个新生成的值,要么就告诉我们它之后都不会再生成新值。当对另一个值的请求到来后,生成器就会从上次离开的位置恢复执行。 普通函数只会返回一个单一值(或者不返回任何值)。而 generator 可以按需一个接一个地返回(“yield”)多个值。
generator 函数
创建一个生成器函数的语法:在关键字 function 后面加上一个星号(*)。生成器函数体内就能够使用新关键字 yield,从而生成独立的值。
调用生成器并不会执行生成器函数,而是会创建一个叫作迭代器(iterator) 的对象。 迭代器用于控制生成器的执行。迭代器对象暴露的最基本接口是 next() 方法,用来向生成器请求一个值,从而控制生成器。 当 next() 方法被调用时,它会恢复生成器的运行,执行直到最近的 yield 语句。然后函数执行暂停,并将产出的(yielded)值返回到外部代码。 next() 的结果始终是一个具有两个属性的对象:
let one = generator.next(); console.log(JSON.stringify(one)); // {value: 1, done: false}
// 再次调用 generator.next(),代码恢复执行并返回下一个 yield 的值 let two = generator.next(); console.log(JSON.stringify(two)); // {value: 2, done: false}
// 第三次调用 generator.next(),代码将会执行到 return 语句 let three = generator.next(); console.log(JSON.stringify(three)); // {value: 3, done: true}, generator 执行完成
这就是 for-of 循环的原理。for-of 循环不过是对迭代器进行迭代的语法糖。
generator 组合(把执行权交给下一个生成器)
正如在标准函数中调用另一个标准函数,我们可以把生成器的执行委托给另一个生成器。
在迭代器上使用 yield* 操作符,程序会跳转到另外一个生成器上执行。 for-of 循环不会关心是否委托到另一个生成器上,它只关心在 done 状态到来之前都一直调用 next() 方法。
generator 双向通信
const result1 = ninjaIterator.next(); console.log(result1.value === "Hattori skulk","Hattori is skulking"); // 触发生成器的执行,并检测返回值是否正确
const result2 = ninjaIterator.next("Hanzo"); // next() 方法传值时,imposter 结果即为该值 console.log(result2.value === "Yoshi (Hanzo) skulk", "We have an imposter!"); // 将数据作为next方法的参数传递给生成 器,并检测返回值是否符合预期
抛出异常
个迭代器除了有一个 next() 方法,还有一个 throw() 方法。
generator 实际应用
用生成器生成ID序列
局部变量 id 仅能在该生成器中被访问,不必担心会不小心在代码的其他地方修改 id 值。随后是一个无限的 while 循环,其每次迭代都能生成一个新 id 值并挂起执行,直到下一次ID请求到达。
生成器内部构成
调用一个生成器不会实际执行它,而是创建了一个新的迭代器,通过该迭代器才能从生成器中请求值。在生成器生成了一个值后,生成器会挂起执行并等待下一个请求的到来。在某种方面来说,生成器的工作更像是一个在状态中运动的状态机。
以上笔记参考《现代 JavaScript 教程》,《JavaScript 忍者秘籍(第2版)》