概要:このチュートリアルでは、JavaScriptイテレータについて学び、イテレータを使用してデータシーケンスをより効率的に処理する方法を学びます。
forループの問題
データの配列がある場合、通常はfor
ループを使用してその要素を反復処理します。たとえば
let ranks = ['A', 'B', 'C'];
for (let i = 0; i < ranks.length; i++) {
console.log(ranks[i]);
}
Code language: JavaScript (javascript)
for
ループは、ranks
配列のインデックスを追跡するために変数i
を使用します。i
の値は、i
の値がranks
配列内の要素数よりも小さい限り、ループが実行されるたびに増加します。
このコードは簡単です。ただし、ループを別のループの中にネストすると、複雑さが増します。さらに、ループ内で複数の変数を追跡すると、エラーが発生しやすくなります。
ES6では、標準ループの複雑さを解消し、ループインデックスの追跡によって引き起こされるエラーを回避するために、for...of
という新しいループ構造が導入されました。
ranks
配列の要素を反復処理するには、次のfor...of
構造を使用します。
for(let rank of ranks) {
console.log(rank);
}
Code language: JavaScript (javascript)
for...of
は、シーケンス内の各要素にアクセスするために配列を反復処理するというコードの真の意図を示すため、for
ループよりもはるかにエレガントです。
さらに、for...of
ループには、配列だけでなく、任意のiterableオブジェクトに対してループを作成する機能があります。
iterableオブジェクトを理解するには、まず反復プロトコルを理解する必要があります。
反復プロトコル
iterableプロトコルとイテレータプロトコルの2つの反復プロトコルがあります。
イテレータプロトコル
オブジェクトは、次の2つの質問に答えるインターフェイス(またはAPI)を実装している場合にイテレータです。
- 要素は残っていますか?
- もしあれば、要素は何ですか?
技術的には、オブジェクトは、次の2つのプロパティを持つオブジェクトを返すnext()
メソッドを持っている場合にイテレータとして認定されます。
-
done
:反復可能な要素がまだあるかどうかを示すブール値。 -
value
:現在の要素。
next()
を呼び出すたびに、コレクション内の次の値が返されます。
{ value: 'next value', done: false }
Code language: CSS (css)
最後の値が返された後にnext()
メソッドを呼び出すと、next()
は結果オブジェクトを次のように返します。
{done: true: value: undefined}
Code language: CSS (css)
done
プロパティの値は、返す値がもうないことを示し、プロパティのvalue
はundefined
に設定されます。
iterableプロトコル
オブジェクトは、引数を受け取らず、イテレータプロトコルに準拠したオブジェクトを返す[Symbol.iterator]
というメソッドが含まれている場合にiterableです。
[Symbol.iterator]
は、ES6の組み込みの既知のシンボルの1つです。
イテレータ
ES6は、コレクション型Array
、Set
、およびMap
の組み込みイテレータを提供しているため、これらのオブジェクトのイテレータを作成する必要はありません。
カスタムタイプがあり、for...of
ループ構造を使用できるようにiterableにしたい場合は、反復プロトコルを実装する必要があります。
次のコードは、後続の数値間のinterval
を持つ(start
、end
)の範囲の数値のリストを返すSequence
オブジェクトを作成します。
class Sequence {
constructor( start = 0, end = Infinity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
[Symbol.iterator]() {
let counter = 0;
let nextIndex = this.start;
return {
next: () => {
if ( nextIndex <= this.end ) {
let result = { value: nextIndex, done: false }
nextIndex += this.interval;
counter++;
return result;
}
return { value: counter, done: true };
}
}
}
};
Code language: JavaScript (javascript)
次のコードでは、for...of
ループでSequence
イテレータを使用します。
let evenNumbers = new Sequence(2, 10, 2);
for (const num of evenNumbers) {
console.log(num);
}
Code language: JavaScript (javascript)
出力
2
4
6
8
10
次のスクリプトに示すように、[Symbol.iterator]()
メソッドに明示的にアクセスできます。
let evenNumbers = new Sequence(2, 10, 2);
let iterator = evenNumbers[Symbol.iterator]();
let result = iterator.next();
while( !result.done ) {
console.log(result.value);
result = iterator.next();
}
Code language: JavaScript (javascript)
クリーンアップ
next()
メソッドに加えて、[Symbol.iterator]()
はオプションでreturn()
というメソッドを返す場合があります。
return()
メソッドは、反復が途中で停止した場合に自動的に呼び出されます。これは、リソースをクリーンアップするコードを配置できる場所です。
次の例では、Sequence
オブジェクトのreturn()
メソッドを実装します。
class Sequence {
constructor( start = 0, end = Infinity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
[Symbol.iterator]() {
let counter = 0;
let nextIndex = this.start;
return {
next: () => {
if ( nextIndex <= this.end ) {
let result = { value: nextIndex, done: false }
nextIndex += this.interval;
counter++;
return result;
}
return { value: counter, done: true };
},
return: () => {
console.log('cleaning up...');
return { value: undefined, done: true };
}
}
}
}
Code language: JavaScript (javascript)
次のスニペットでは、Sequence
オブジェクトを使用して、1から10までの奇数のシーケンスを生成します。ただし、反復が途中で停止します。その結果、return()
メソッドが自動的に呼び出されます。
let oddNumbers = new Sequence(1, 10, 2);
for (const num of oddNumbers) {
if( num > 7 ) {
break;
}
console.log(num);
}
Code language: JavaScript (javascript)
出力
1
3
5
7
cleaning up...
このチュートリアルでは、JavaScriptイテレータと、反復プロトコルを使用してカスタム反復ロジックを実装する方法について学びました。