この記事では、JavaScript のフロントエンド イテレータとジェネレータについての関連知識を紹介します。必要な方は参考にしていただければ幸いです。
フロントエンド (vue) マスタリーコースへのエントリー: ラーニングに入る
Iterator は、さまざまなデータ構造に統合されたアクセス メカニズムを提供する統合インターフェイス メカニズムを提供します。
Iterator を定義すると、 next() メソッドが呼び出されるたびに、結果オブジェクトが返されます。value は現在の値を表し、done は走査が完了したかどうかを表します。 。
関数 makeIterator(Array){ インデックス = 0 とします。 戻る { 次へ: function(){ 戻る ( 配列の長さ > インデックス ? {値: 配列[インデックス++]}: {完了: true} ) } } } let iterator = makeIterator(['1','2']) console.log(iterator.next()); // {値: '1'} console.log(iterator.next()); // {値: '2'} console.log(iterator.next()); // {完了: true}
イテレータの役割:
さまざまなデータ構造に統一されたシンプルなアクセス インターフェイスを提供します。
データ構造のメンバーを特定の順序で配置できるようにします。
~による消費のため
ES6 には、イテレータ オブジェクトを走査するための for of ステートメントが用意されています。 for of ステートメントを使用して、上で作成したイテレータを走査します。
let iterator = makeIterator(['1','2']) for (イテレータの値を許可) { console.log(値); } // イテレータは反復可能ではありません
結果として、イテレータが反復可能ではないというエラーが表示されます。これはなぜでしょうか。 ES6 では、データ構造に Symbol.iterator プロパティがある場合、デフォルトの Iterator インターフェイスがデータ構造の Symbol.iterator プロパティに展開されることが規定されています。
カスタムの makeIterator を次のように変換します。
const MakeIterator = (配列) => ({ [Symbol.iterator](){ インデックス = 0 とします。 戻る { 次(){ 長さを = Array.length; にします。 if(インデックス < 長さ){ return {値: 配列[index++]} }それ以外{ 戻り値 {完了: true} } } } } }) for(MakeIterator([1,2])の値をレット)){ console.log(値) } // 1 // 2
makeIterator に return メソッドを追加します。for...of ループが早期に終了した場合 (通常はエラーまたは Break ステートメントが原因)、return() メソッドが呼び出されてトラバーサルが終了します。
この機能に基づいて、トラバーサルを完了する前にオブジェクトがリソースをクリーンアップまたは解放する必要がある場合、return() メソッドをデプロイし、ファイルの読み取りが失敗したときにファイルを閉じる機能を含めることができます。
const MakeIterator = (配列) => ({ [Symbol.iterator](){ インデックス = 0 とします。 戻る { 次(){ 長さを = Array.length; にします。 if(インデックス < 長さ){ return {値: 配列[index++]} }それ以外{ 戻り値 {完了: true} } }、 戻る(){ 戻り値 {完了: true} } } } }) for(MakeIterator([1, 2, 3])の値を許可)){ console.log(value) // 1 // 方法 1 壊す; // 方法 2 // 新しい Error('error') をスローします。 }
配列
セット
地図
配列のようなオブジェクト (arguments オブジェクト、DOM NodeList オブジェクト、typedArray オブジェクトなど)
// 引数オブジェクト関数 sum(){ for(let 引数の値){ console.log(値) } } 合計(1,2) // 1 // 2 // typedArray オブジェクト let typeArry = new Int8Array(2); タイプArry[0] = 1; タイプArry[1] = 2; for(let 値 typeArry){ console.log(値) } // 1 // 2
ジェネレーターオブジェクト
関数*gen(){ 収量1; 収量2; } for(gen() の値を許可){ console.log(値) }
弦
Q: Object にネイティブ Iterator がないのはなぜですか?
A: Object がデフォルトで Iterator インターフェイスを展開しない理由は、オブジェクトのどのプロパティが最初に走査され、どのプロパティが後で走査されるかが不確かであるためです。
本質的に、トラバーサーは線形プロセスです。非線形データ構造の場合、トラバーサー インターフェイスの展開は線形変換の展開と同じです。
ただし、現時点ではオブジェクトは実際に Map 構造として使用されるため、厳密に言えば、ES5 には Map 構造がありませんが、ES6 ではネイティブに提供されているため、オブジェクト デプロイメント トラバーサー インターフェイスは必要ありません。
代入の分割
let set = new Set().add('a').add('b').add('c'); let [x,y] = セット // x='a';
スプレッド演算子
var str = 'こんにちは'; [...str] // ['h','e','l','l','o']
Spread 演算子は Iterator インターフェイスを呼び出すため、Object は Iterator インターフェイスをデプロイしません。なぜ... 演算子が使用できるのでしょうか?
理由: スプレッド演算子には 2 種類あります
1 つは関数パラメータと配列拡張の場合に使用されます。この場合、オブジェクトは反復可能 (反復可能) である必要があります。
もう 1 つはオブジェクト展開用です。つまり、{...obj} 形式の場合、オブジェクトは列挙可能 (enumerable) である必要があります。
obj1 = { にします 名前:「銭迅」 } obj2 = { にします 年齢: 3歳 } // 配列オブジェクトは列挙可能です let obj = {...obj1, ...obj2} console.log(obj) //{名前: 'qianxun'、年齢: 3} // 通常のオブジェクトはデフォルトでは反復可能ではありません let obj = [...obj1, ...obj2] console.log(obj) // オブジェクトは反復可能ではありません
関数 forOf(obj, cb){ let iteratorValue = obj[Symbol.iterator](); let result = iteratorValue.next() while(!result.done){ cb(結果.値) 結果 = iteratorValue.next() } } forOf([1,2,3], (値)=>{ console.log(値) }) // 1 // 2 // 3
概念的には
ジェネレーター機能は、ES6 が提供する非同期プログラミング ソリューションです。 Generator 関数は、複数の内部状態をカプセル化するステート マシンです。
Generator 関数はトラバーサー オブジェクト生成関数でもあり、実行後にトラバーサー オブジェクトを返します。
フォーマル
1. 関数キーワードと関数名の間にはアスタリスクがあります。
2. Yield 式は関数本体内で使用され、さまざまな内部状態を定義します。
関数* simpleGenerator(){ 収量1; 収量2; } simpleGenerator()
上記のように、単純なジェネレーターを作成し、それを 2 つの質問で調べました。
ジェネレーター関数の実行後はどうなりますか?
関数内の yield 式は何をするのでしょうか?
関数* simpleGenerator(){ console.log('hello world'); 収量1; 収量2; } let ジェネレータ = simpleGenerator(); // simpleGenerator {<一時停止中}} console.log(generator.next()) // こんにちは世界 // {値: 1、完了: false} console.log(generator.next()) // {値: 2、完了: false}
Generator ジェネレータ関数は実行後にジェネレータ オブジェクトを返しますが、通常の関数はジェネレータ オブジェクトの次のメソッドが呼び出されるたびに関数内のコードを直接実行し、次の yield キーワードが実行を停止するまで関数が実行されます。 {値: 値、完了: ブール値} オブジェクト。
yield 式自体には戻り値がないか、常に unknown が返されます。次のメソッドは 1 つのパラメータを取ることができ、それは前の yield 式の戻り値として扱われます。次のメソッドのパラメーターを通じて、ジェネレーター関数のさまざまな段階で外部から内部にさまざまな値を注入し、関数の動作を調整できます。 next メソッドのパラメータは前の yield 式の戻り値を表すため、next メソッドを初めて使用するときはパラメータを渡すことは無効です。
関数 sum(x){ 戻り関数(y){ x + y を返します。 } } console.log(sum(1)(2)) // 次のパラメータを使用して function* sum(x){ を書き換えます y = 収量 x とします。 while(true){ y = 収量 x + y; } } gen = sum(2) とします console.log(gen.next()) // 2 console.log(gen.next(1)) // 3 console.log(gen.next(2)) // 4
yield 式の役割: 内部状態の定義と実行の一時停止、yield 式と return ステートメントの違い。
yield 式は、関数が実行を一時停止し、次回その位置から逆方向に実行を続けることを意味しますが、return ステートメントには位置を記憶する機能がありません。
関数では、return ステートメントは 1 つだけ実行できますが、複数の yield 式を実行できます。
return ステートメントはどの関数でも使用できます。yield 式はジェネレーター関数でのみ使用できます。他の場所で使用すると、エラーが報告されます。
yield 式が演算に関与する場合は、括弧で囲みます。関数パラメーターとして使用する場合、または代入式の右側に配置する場合は、括弧を含めなくても構いません。
関数 *gen() { console.log('hello' + yield) × console.log('hello' + (yield)) √ console.log('hello' + yield 1) × console.log('hello' + (yield 1)) √ foo(収量1) √ const param = 収量 2 √ }
Generator ジェネレーター関数が複数の出力をサポートできるという事実に基づいて、関数が複数の戻り値を持つシナリオを実装できます。
関数* gen(num1, num2){ num1 + num2 を生成します。 num1 - num2 を生成します。 } res = gen(2, 1); とします。 console.log(res.next()) // {値: 3、完了: false} console.log(res.next()) // {値: 1、完了: false}
Generator 関数は反復子生成関数であるため、オブジェクトの Symbol.iterator プロパティに Generator を割り当てることができ、その結果、オブジェクトは Iterator インターフェイスを持つようになります。ジェネレーターの実装コードはより簡潔です。
obj = { にします 名前: 'qianxun'、 年齢:3歳、 [Symbol.iterator]: function(){ それを = これとします。 let キー = Object.keys(that) インデックス = 0 とします。 戻る { 次へ: function(){ インデックス < キーの長さを返しますか? {値: that[keys[index++]]、完了: false}: {値: 未定義、完了: true} } } } } for(obj の値を許可){ console.log(値) }
ジェネレータ:
obj = { にします 名前: 'qianxun'、 年齢:3歳、 [Symbol.iterator]: function* (){ let key = Object.keys(this) for(let i=0; i< key.length; i++){ this[keys[i]] を生成します。 } } } for(obj の値を許可){ console.log(値) }
return()
メソッドは、指定された値を返し、トラバーサル ジェネレーター関数を終了できます。
関数*gen() { 収量1; 収量2; 収量3; } var g = gen(); g.next() // { 値: 1、完了: false } // return() メソッドの呼び出し時にパラメータが指定されていない場合、戻り値の value 属性は未定義です g.return('foo') // { 値: "foo"、完了: true } g.next() // { 値: 未定義、完了: true }
Generator 関数内にtry...finally
コード ブロックがあり、 try
コード ブロックが実行されている場合、 return()
メソッドにより、実行後すぐにfinally
コード ブロックが開始され、関数全体が終了します。
関数* 数値 () { 収量1; 試す { 収量2; 収量3; } ついに { 収量4; 収量5; } 収量6; } var g = 数値(); g.next() // { 値: 1、完了: false } g.next() // { 値: 2、完了: false } g.return(7) // { 値: 4、完了: false } g.next() // { 値: 5、完了: false } g.next() // { 値: 7、完了: true }
Generator 関数内で別の Generator 関数を呼び出したい場合。前者の関数本体内でのトラバースを手動で完了する必要がある場合、関数呼び出しが複数のレベルでネストされている場合、記述が煩雑になり、ES6 ではその解決策として yield* 式が提供されます。
他のジェネレーターに委任する
関数* g1() { 収量2; 収量3; } 関数* g2() { 収量1; 収量* g1(); 収量4; } const イテレータ = g2(); console.log(iterator.next()); // { 値: 1、完了: false } console.log(iterator.next()); // { 値: 2、完了: false } console.log(iterator.next()); // { 値: 3、完了: false } console.log(iterator.next()); // { 値: 4、完了: false } console.log(iterator.next()); // { 値: 未定義、完了: true }
他の反復可能なオブジェクトへのデリゲート
関数*gen(){ 収量* [1,2,3] } console.log(gen().next()) // {値: 1、完了: false}
Generator 関数はトラバーサーを返します。ES6 では、このトラバーサーが Generator 関数のインスタンスであり、Generator.prototype オブジェクトのメソッドを継承することが規定されていますが、現時点ではインスタンスではなくグローバル オブジェクトであるため、このオブジェクトのプロパティを取得できません。物体。
関数*gen(){ this.a = 1 } gen.prototype.say = function(){ console.log('こんにちは') } obj = gen() とします console.log(obj instanceof gen) // true obj.say() // こんにちは obj.next() console.log(obj.a) //未定義
コンストラクターのようなインスタンス プロパティにアクセスしたい場合は、これを変更して Generator.prototype にバインドできます。
関数*gen(){ this.a = 1 } gen.prototype.say = function(){ console.log('こんにちは') } let obj = gen.call(gen.prototype) console.log(obj instanceof gen) // true obj.say() // こんにちは obj.next() console.log(obj.a) //1
関数* StateMachine(状態){ 移行しましょう。 while(true){ if(transition === "INCREMENT"){ 状態++; }else if(transition === "DECREMENT"){ 州 - ; } 遷移 = 降伏状態; } } const iterator = StateMachine(0); console.log(iterator.next()); // 0 console.log(iterator.next('INCREMENT')); // 1 console.log(iterator.next('DECREMENT')); // 0