この記事では、JavaScript に関する関連知識を紹介します。主に、JS ジェネレーター関数の yield 式の例の詳細な説明を紹介します。ジェネレーター関数は、ES6 によって提供される非同期プログラミング ソリューションです。ぜひご覧ください。皆さんのお役に立てば幸いです。
[関連する推奨事項: JavaScript ビデオ チュートリアル 、Web フロントエンド ]
JavaScript では、関数が実行を開始すると、最後まで実行されるか、return に遭遇したときに終了します。実行中に他のコードが中断することはできず、関数本体に値を渡すことはできません。外部
Generator 関数 (ジェネレーター) の出現により、関数の完全な操作を中断することが可能になり、その文法動作は従来の関数とは完全に異なります
Generator 関数は非同期関数ですES6 によって提供されるプログラミング ソリューションであり、これも形式的には通常の関数ですが、いくつかの注目すべき機能があります:
// 传统函数 function foo() { return 'hello world' } foo() // 'hello world',一旦调用立即执行 // Generator函数 function* generator() { yield 'status one' // yield 表达式是暂停执行的标记 return 'hello world' } let iterator = generator() // 调用 Generator函数,函数并没有执行,返回的是一个Iterator对象 iterator.next() // {value: "status one", done: false},value 表示返回值,done 表示遍历还没有结束 iterator.next() // {value: "hello world", done: true},value 表示返回值,done 表示遍历结束
従来の関数は上記のコードで確認できます Generator関数の動作とは全く異なります 従来関数は呼び出し直後に実行され戻り値が出力されますが、Generator関数は実行されずにIteratorオブジェクトを返しますIterator オブジェクトの next メソッドを呼び出すことでトラバースされます。関数本体での実行がわかります。「移動する前にキックされている」ように感じられます。
function* gen() { yield 'hello' yield 'world' return 'ending' } let it = gen() it.next() // {value: "hello", done: false} it.next() // {value: "world", done: false} it.next() // {value: "ending", done: true} it.next() // {value: undefined, done: true}
上記のコードは、2 つのジェネレーター関数を定義しています。 yield 式と return ステートメント (つまり、3 つの状態が生成されます)
Iterator オブジェクトの次のメソッドが呼び出されるたびに、内部ポインターは関数の先頭または関数が配置された場所から実行を開始します。一時停止する次の yield 式または return ステートメントに遭遇するまで、前回停止します。つまり、ジェネレーター関数はセクション単位で実行され、yield 式は実行を一時停止するためのマークであり、次のメソッドは実行を再開できます。トラバーサルが完了すると、他の状態が存在しないため、{value: unfined、done: true} が返されます。次のメソッドの呼び出しを続けると、この値が返されます。
yield 式
function (){ yield 1; })() // SyntaxError: Unexpected number // 在一个普通函数中使用yield表达式,结果产生一个句法错误
yield 式を別の式で使用する場合は、括弧内に配置する必要があります
function* demo() { console.log('Hello' + yield); // SyntaxError console.log('Hello' + yield 123); // SyntaxError console.log('Hello' + (yield)); // OK console.log('Hello' + (yield 123)); // OK }
yield 式はパラメータとして使用されるか、代入式の右側に配置され、括弧は必要ありません
function* demo() { foo(yield 'a', yield 'b'); // OK let input = yield; // OK }
yield 式と return ステートメントの違い
違い:
Every yield に遭遇すると、関数は実行を一時停止し、次回その位置から逆方向に実行を続けます。また、return ステートメントには位置を記憶する機能がありません。function* foo() { yield 'aaa' yield 'bbb' } function* bar() { foo() yield 'ccc' yield 'ddd' } let iterator = bar() for(let value of iterator) { console.log(value) } // ccc // ddd
上記の例では、関数 bar によって生成されたトラバーサー オブジェクトをトラバースするために for...of を使用する場合、bar 自体の 2 つのステータス値のみが返されます。このとき、bar で foo を正しく呼び出したい場合は、yield* 式を使用する必要があります。
yield* 式は、Generator 関数内で別の Generator 関数を
execute するために使用されます function* foo() {
yield 'aaa'
yield 'bbb'
}
function* bar() {
yield* foo() // 在bar函数中 **执行** foo函数
yield 'ccc'
yield 'ddd'
}
let iterator = bar()
for(let value of iterator) {
console.log(value)
}
// aaa
// bbb
// ccc
// ddd
function* gen(x) { let y = 2 * (yield (x + 1)) // 注意:yield 表达式如果用在另一个表达式中,必须放在圆括号里面 let z = yield (y / 3) return x + y + z } let it = gen(5) /*** 正确的结果在这里 ***/ console.log(it.next()) // 首次调用next,函数只会执行到 “yield(5+1)” 暂停,并返回 {value: 6, done: false} console.log(it.next()) // 第二次调用next,没有传递参数,所以 y的值是undefined,那么 y/3 当然是一个NaN,所以应该返回 {value: NaN, done: false} console.log(it.next()) // 同样的道理,z也是undefined,6 + undefined + undefined = NaN,返回 {value: NaN, done: true}
次のメソッドにパラメータが指定された場合、戻り結果はまったく異なります
{ function* gen(x) { let y = 2 * (yield (x + 1)) // 注意:yield 表达式如果用在另一个表达式中,必须放在圆括号里面 let z = yield (y / 3) return x + y + z } let it = gen(5) console.log(it.next()) // 正常的运算应该是先执行圆括号内的计算,再去乘以2,由于圆括号内被 yield 返回 5 + 1 的结果并暂停,所以返回{value: 6, done: false} console.log(it.next(9)) // 上次是在圆括号内部暂停的,所以第二次调用 next方法应该从圆括号里面开始,就变成了 let y = 2 * (9),y被赋值为18,所以第二次返回的应该是 18/3的结果 {value: 6, done: false} console.log(it.next(2)) // 参数2被赋值给了 z,最终 x + y + z = 5 + 18 + 2 = 25,返回 {value: 25, done: true} }
Iterator インターフェイスとの関係
Symbol.iterator プロパティ自体は関数であり、現在のデータ構造のデフォルトのトラバーサー生成関数です。この関数を実行すると、トラバーサーが返されます。
Generator 関数を実行すると実際には反復子が返されるため、オブジェクトの Symbol.iterator プロパティに Generator を割り当てて、オブジェクトに Iterator インターフェイスを持たせることができます。
{ let obj = {} function* gen() { yield 4 yield 5 yield 6 } obj[Symbol.iterator] = gen for(let value of obj) { console.log(value) } // 4 // 5 // 6 console.log([...obj]) // [4, 5, 6] }
传统对象没有原生部署 Iterator接口,不能使用 for...of 和 扩展运算符,现在通过给对象添加Symbol.iterator
属性和对应的遍历器生成函数,就可以使用了
由于 Generator 函数运行时生成的是一个 Iterator 对象,因此,可以直接使用 for...of 循环遍历,且此时无需再调用 next() 方法
这里需要注意,一旦 next() 方法的返回对象的 done 属性为 true,for...of 循环就会终止,且不包含该返回对象
{ function* gen() { yield 1 yield 2 yield 3 yield 4 return 5 } for(let item of gen()) { console.log(item) } // 1 2 3 4 }
Generator 函数返回的遍历器对象,还有一个 return 方法,可以返回给定的值(若没有提供参数,则返回值的value属性为 undefined),并且 终结 遍历 Generator 函数
{ function* gen() { yield 1 yield 2 yield 3 } let it = gen() it.next() // {value: 1, done: false} it.return('ending') // {value: "ending", done: true} it.next() // {value: undefined, done: true} }
应用一:假定某公司的年会上有一个抽奖活动,总共6个人可以抽6次,每抽一次,抽奖机会就会递减
按照常规做法就需要声明一个全局的变量来保存剩余的可抽奖次数,而全局变量会造成全局污染,指不定什么时候就被重新赋值了,所以往往并不被大家推荐
{ let count = 6 // 声明一个全局变量 // 具体抽奖逻辑的方法 function draw() { // 执行一段抽奖逻辑 // ... // 执行完毕 console.log(`剩余${count}次`) } // 执行抽奖的方法 function startDrawing(){ if(count > 0) { count-- draw(count) } } let btn = document.createElement('button') btn.id = 'start' btn.textContent = '开始抽奖' document.body.appendChild(btn) document.getElementById('start').addEventListener('click', function(){ startDrawing() }, false) }[object Object]
事实上,抽奖通常是每个人自己来抽,每抽一次就调用一次抽奖方法,而不是点一次就一次性就全部运行完,是可暂停的,这个不就是 Generator 函数的意义所在吗?
// 具体抽奖逻辑的方法 function draw(count) { // 执行一段抽奖逻辑 // ... console.log(`剩余${count}次`) } // 执行抽奖的方法 function* remain(count) { while(count > 0) { count-- yield draw(count) } } let startDrawing = remain(6) let btn = document.createElement('button') btn.id = 'start' btn.textContent = '开始抽奖' document.body.appendChild(btn) document.getElementById('start').addEventListener('click', function(){ startDrawing.next() }, false)
应用二:由于HTTP是一种无状态协议,执行一次请求后服务器无法记住是从哪个客户端发起的请求,因此当需要实时把服务器数据更新到客户端时通常采用的方法是长轮询和Websocket。这里也可以用 Generator 函数来实现长轮询
{ // 请求的方法 function* ajax() { yield new Promise((resolve, reject) => { // 此处用一个定时器来模拟请求数据的耗时,并约定当返回的json中code为0表示有新数据更新 setTimeout(() => { resolve({code: 0}) }, 200) }) } // 长轮询的方法 function update() { let promise = ajax().next().value // 返回的对象的value属性是一个 Promise 实例对象 promise.then(res => { if(res.code != 0) { setTimeout(() => { console.log('2秒后继续查询.....') update() }, 2000) } else{ console.log(res) } }) } update() }
【相关推荐:JavaScript视频教程、web前端】
以上がJavaScriptでのジェネレータ関数yield式例の詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。