反復可能オブジェクトは配列を一般化したものです。これは、あらゆるオブジェクトをfor..of
ループで使用できるようにする概念です。
もちろん、配列は反復可能です。しかし、他にも同様に反復可能な組み込みオブジェクトが多数あります。たとえば、文字列も反復可能です。
オブジェクトが厳密には配列ではなく、何かのコレクション (リスト、セット) を表す場合、 for..of
はそれをループするための優れた構文なので、それを機能させる方法を見てみましょう。
独自のイテラブルを作成すると、イテラブルの概念を簡単に理解できます。
たとえば、配列ではないが、 for..of
に適しているように見えるオブジェクトがあります。
数値の間隔を表すrange
オブジェクトのように:
範囲 = { にします から: 1、 まで: 5 }; // for..of が機能するようにします。 // for(let num of range) ... num=1,2,3,4,5
range
オブジェクトを反復可能にする (つまり、 for..of
機能させる) には、 Symbol.iterator
(そのための特別な組み込みシンボル) という名前のオブジェクトにメソッドを追加する必要があります。
for..of
が開始されると、そのメソッドが 1 回呼び出されます (見つからない場合はエラーになります)。メソッドはイテレータ、つまりメソッドnext
を持つオブジェクトを返す必要があります。
以降、 for..of
返された object に対してのみ機能します。
for..of
次の値を必要とする場合、そのオブジェクトに対してnext()
を呼び出します。
next()
の結果は{done: Boolean, value: any}
の形式でなければなりません。done done=true
ループが終了することを意味し、それ以外の場合はvalue
が次の値になります。
注釈付きのrange
の完全な実装は次のとおりです。
範囲 = { にします から: 1、 まで: 5 }; // 1. for..of への呼び出しは最初にこれを呼び出します range[Symbol.iterator] = function() { // ...イテレータ オブジェクトを返します。 // 2. 次に、for..of は以下の反復子オブジェクトでのみ機能し、次の値を要求します。 戻る { 現在: this.from、 最後: this.to、 // 3. next() は for..of ループによって反復ごとに呼び出されます 次() { // 4. 値をオブジェクトとして返す必要があります {done:..., value :...} if (this.current <= this.last) { return { 完了: false、値: this.current++ }; } それ以外 { 戻り値 { 完了: true }; } } }; }; // これで動作します! for (範囲の数値を指定します) { アラート(番号); // 1、次に 2、3、4、5 }
イテラブルの中心的な機能である関心の分離に注意してください。
range
自体にはnext()
メソッドがありません。
代わりに、 range[Symbol.iterator]()
の呼び出しによって別のオブジェクト、いわゆる「反復子」が作成され、そのnext()
反復の値を生成します。
したがって、イテレータ オブジェクトは、それが反復処理するオブジェクトとは別のものです。
技術的には、コードを簡素化するために、それらをマージし、 range
自体をイテレータとして使用することもできます。
このような:
範囲 = { にします から: 1、 に: 5、 [Symbol.iterator]() { this.current = this.from; これを返します。 }、 次() { if (this.current <= this.to) { return { 完了: false、値: this.current++ }; } それ以外 { 戻り値 { 完了: true }; } } }; for (範囲の数値を指定します) { アラート(番号); // 1、次に 2、3、4、5 }
現在、 range[Symbol.iterator]()
range
オブジェクト自体を返します。必要なnext()
メソッドがあり、現在の反復の進行状況をthis.current
に記憶しています。もっと短い?はい。そして時にはそれもまた良いのです。
欠点は、オブジェクトに対して 2 つのfor..of
ループを同時に実行することが不可能になったことです。反復子が 1 つ (オブジェクト自体) しかないため、ループは反復状態を共有することになります。ただし、非同期シナリオであっても、2 つの for-of が並行して実行されることはまれです。
無限反復子
無限イテレータも可能です。たとえば、 range.to = Infinity
の場合、 range
無限になります。あるいは、無限の擬似乱数シーケンスを生成する反復可能なオブジェクトを作成することもできます。役に立つこともあります。
next
には制限がなく、より多くの値を返すことができますが、これは正常です。
もちろん、そのような反復可能オブジェクトに対するfor..of
ループは無限に行われます。ただし、 break
使用していつでも停止できます。
配列と文字列は、組み込み反復可能オブジェクトとして最も広く使用されています。
文字列の場合、 for..of
その文字をループします。
for (let char of "test") { // 各キャラクターに 1 回ずつ、計 4 回トリガーします アラート( char ); // t、次に e、次に s、その後 t }
そして、サロゲートペアでも正しく動作します。
str = '??'; for (let char of str) { アラート( char ); // ?、 その後 ? }
より深く理解するために、イテレータを明示的に使用する方法を見てみましょう。
for..of
とまったく同じ方法で文字列を反復処理しますが、直接呼び出します。このコードは文字列反復子を作成し、そこから「手動で」値を取得します。
let str = "こんにちは"; // と同じことをします // for (let char of str)alert(char); let iterator = str[Symbol.iterator](); while (true) { let result = iterator.next(); if (result.done) ブレーク; アラート(結果.値); // 文字を1文字ずつ出力します }
これが必要になることはほとんどありませんが、 for..of
よりもプロセスをより詳細に制御できます。たとえば、反復プロセスを分割できます。少し反復してから停止し、別のことを実行して、後で再開します。
2 つの公式用語は似ていますが、大きく異なります。混乱を避けるために、それらをよく理解してください。
Iterable は、上で説明したように、 Symbol.iterator
メソッドを実装するオブジェクトです。
配列のようなオブジェクトはインデックスとlength
持つオブジェクトなので、配列のように見えます。
ブラウザーやその他の環境で実際のタスクに JavaScript を使用すると、反復可能オブジェクト、配列のようなオブジェクト、あるいはその両方のオブジェクトに遭遇することがあります。
たとえば、文字列は反復可能 ( for..of
機能する) であり、配列のようなもの (数値インデックスとlength
持つ) の両方です。
ただし、反復可能は配列のようではない場合があります。逆も同様で、配列のようなものは反復可能ではない可能性があります。
たとえば、上記の例のrange
は反復可能ですが、インデックス付きのプロパティとlength
がないため、配列のようにはなりません。
そして、これは配列に似ていますが、反復可能ではないオブジェクトです。
let arrayLike = { // インデックスと長さがある => 配列のようなもの 0: 「こんにちは」、 1:「世界」、 長さ: 2 }; // エラー (Symbol.iterator なし) for (arrayLike の項目を許可) {}
iterable と array-like はどちらも通常はarray ではなく、 push
やpop
などを持ちません。そのようなオブジェクトがあり、それを配列と同様に操作したい場合、これはかなり不便です。たとえば、配列メソッドを使用してrange
を操作したいとします。それを達成するにはどうすればよいでしょうか?
反復可能な値または配列のような値を受け取り、そこから「実際の」 Array
を作成するユニバーサル メソッド Array.from があります。次に、それに対して配列メソッドを呼び出すことができます。
例えば:
let arrayLike = { 0: 「こんにちは」、 1:「世界」、 長さ: 2 }; let arr = Array.from(arrayLike); // (*) アラート(arr.pop()); // ワールド (メソッドは機能します)
行(*)
のArray.from
オブジェクトを取得し、それが反復可能であるか配列のようなものであるかを検査し、新しい配列を作成してすべての項目をそこにコピーします。
同じことが反復可能でも起こります。
// 範囲が上記の例から取得されたものと仮定します let arr = Array.from(range); アラート(arr); // 1,2,3,4,5 (配列から文字列への変換は機能します)
Array.from
の完全な構文では、オプションの「マッピング」関数を提供することもできます。
Array.from(obj[, mapFn, thisArg])
オプションの 2 番目の引数mapFn
配列に要素を追加する前に各要素に適用される関数にすることができ、 thisArg
使用してthis
設定できます。
例えば:
// 範囲が上記の例から取得されたものと仮定します // 各数値を二乗する let arr = Array.from(range, num => num * num); アラート(arr); // 1,4,9,16,25
ここではArray.from
使用して文字列を文字の配列に変換します。
str = '??'; // str を文字の配列に分割します let chars = Array.from(str); アラート(chars[0]); //? アラート(chars[1]); //? アラート(chars.length); // 2
str.split
とは異なり、文字列の反復可能な性質に依存しているため、 for..of
と同様にサロゲート ペアで正しく動作します。
技術的には、ここでは次と同じことを行います。
str = '??'; chars = []; とします。 // Array.from は内部的に同じループを実行します for (let char of str) { chars.push(char); } アラート(文字);
…でも、短いですよ。
サロゲート対応slice
をその上に構築することもできます。
関数スライス(str、開始、終了) { return Array.from(str).slice(start, end).join(''); } str = '???'; アラート(スライス(str, 1, 3) ); // ?? // ネイティブ メソッドはサロゲート ペアをサポートしていません アラート( str.slice(1, 3) ); // ガベージ (異なるサロゲート ペアからの 2 つの部分)
for..of
で使用できるオブジェクトはiterableと呼ばれます。
技術的には、イテラブルはSymbol.iterator
という名前のメソッドを実装する必要があります。
obj[Symbol.iterator]()
の結果はイテレータと呼ばれます。さらなる反復プロセスを処理します。
イテレータには、オブジェクト{done: Boolean, value: any}
を返すnext()
という名前のメソッドが必要です。ここで、 done:true
反復プロセスの終了を示し、それ以外の場合はvalue
が次の値になります。
Symbol.iterator
メソッドはfor..of
によって自動的に呼び出されますが、直接呼び出すこともできます。
文字列や配列などの組み込み反復可能オブジェクトもSymbol.iterator
を実装します。
文字列反復子はサロゲート ペアについて知っています。
インデックス付きのプロパティとlength
持つオブジェクトは、配列のようなオブジェクトと呼ばれます。このようなオブジェクトには他のプロパティやメソッドもありますが、配列の組み込みメソッドがありません。
仕様の内部を見てみると、ほとんどの組み込みメソッドは、より抽象的であるため、「実際の」配列ではなく、イテラブルまたは配列のようなものを操作することを前提としていることがわかります。
Array.from(obj[, mapFn, thisArg])
反復可能または配列のようなobj
から実際のArray
を作成し、それに対して配列メソッドを使用できます。オプションの引数mapFn
とthisArg
使用すると、各項目に関数を適用できます。