JavaScriptでのジェネレータ関数yield式例の詳細説明

WBOY
リリース: 2022-11-01 17:04:16
転載
1682 人が閲覧しました

この記事では、JavaScript に関する関連知識を紹介します。主に、JS ジェネレーター関数の yield 式の例の詳細な説明を紹介します。ジェネレーター関数は、ES6 によって提供される非同期プログラミング ソリューションです。ぜひご覧ください。皆さんのお役に立てば幸いです。

[関連する推奨事項: JavaScript ビデオ チュートリアル Web フロントエンド ]

ジェネレーターとはfunction

JavaScript では、関数が実行を開始すると、最後まで実行されるか、return に遭遇したときに終了します。実行中に他のコードが中断することはできず、関数本体に値を渡すことはできません。外部

Generator 関数 (ジェネレーター) の出現により、関数の完全な操作を中断することが可能になり、その文法動作は従来の関数とは完全に異なります

Generator 関数は非同期関数ですES6 によって提供されるプログラミング ソリューションであり、これも形式的には通常の関数ですが、いくつかの注目すべき機能があります:

  • 関数キーワードと関数名の間にはアスタリスク "*" があります (関数キーワードと関数名の間にはアスタリスク "*" があります ( function キーワードの隣に配置します)
  • 関数本体で yield 式を使用して、さまざまな内部状態を定義します (複数の yield が存在する可能性があります)
  • Generator 関数を直接呼び出しても実行されません。実行結果を返しますが、イテレータ オブジェクト (イテレータ オブジェクト) を返します。
  • ジェネレータ関数内の各状態を横断して、イテレータ オブジェクトの次のメソッドを順番に呼び出します。
  // 传统函数
  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 式

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 ステートメントには位置を記憶する機能がありません。
  • 関数は return ステートメントを 1 回しか実行できません。ジェネレーター関数内には、yield
  • yield* 式をいくつでも指定できます。

ジェネレーター関数内で別のジェネレーター関数を呼び出しても、デフォルトでは効果がありません

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
ログイン後にコピー
next() メソッドのパラメータ

yield 式自体には戻り値がないか、常に unknown が返されます。次のメソッドはパラメータを受け取ることができ、そのパラメータは前の yield 式の戻り値として扱われます

  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 インターフェイスとの関係

ES6 では、デフォルトの Iterator インターフェイスがデータ構造の Symbol.iterator プロパティにデプロイされること、つまりデータ構造に Symbol.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 属性和对应的遍历器生成函数,就可以使用了

for...of 循环

由于 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.prototype.return()

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}
}
ログイン後にコピー


Generator 函数应用举例

应用一:假定某公司的年会上有一个抽奖活动,总共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 サイトの他の関連記事を参照してください。

関連ラベル:
ソース:jb51.net
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート