MENU

構文繰り返し・反復処理

【JavaScript】 Iterator(イテレーター)とは?避けて通りたいけど説明してみる

更新日:2020/02/29

JavaScriptにはイテレーターという機能があって、反復処理に使うと便利らしい。
別に知らなくても問題ないが…

 

今回は勉強がてら、イテレーターについて解説してみようと思う。

 

Iterator(イテレーター)とは?

 

JavaScriptでのイテレーターとは、次の4つの条件を満たすオブジェクトです。

 

条件1: next() というメソッドを持つ

 

条件2: next()を呼び出すたびに、順番に値を返す

 

条件3: 値は次のオブジェクトで返す

 

      { value: 値, done: false }

 

条件4: 返す値がない(終了した)場合、次のオブジェクトを返す

 

      { value: 戻り値, done: true }
      (戻り値はイテレーターの実行結果です。なくても問題ありません。)

 

この条件を元に、簡単なイテレーターを作成してみます。

 

イテレーター例

let a = {
     count: 0,
     next: function() {
          this.count ++;
          return ( this.count <= 5 ) ?
                    { value: this.count * 2 , done: false }
                    : { done: true};
     }
};

 

2から10までの偶数値をnext()を呼び出すたびに返すオブジェクトです。

 

このイテレーターは次のように使用します。

 

イテレーター使用例

let itr = a.next();
while ( !itr.done ) {
    console.log(itr.value); // 結果: 2
                                    //         4
                                    //         6
                                    //         8
                                    //         10
    itr = a.next();
}

 

whileループでdoneの値をチェックしながら、value値を表示しています。

 

簡単ですね。
ですが実はこれが、イテレーターを理解するための大きなハードルです。
すなわち・・・

 

イテレーターって意味なくない?

 

 

自分でループさせるなら、自分なりの実装でいいですもんね。

 

 

だから何なんだろう?

 

これだけなら、イテレーターは無駄な知識です。

 

しかしイテラブルなオブジェクトとして実装することで、便利に使用できるようになります。

 

iterable(イテラブル)とは

 

イテラブルなオブジェクトとは、イテレーターを作成して返す [Symbol.iterator]() というメソッドを持つオブジェクトです。
ArrayやStringなどもイテラブルなオブジェクトです。

 

 

イテラブルなオブジェクト

 

先ほどのイテレーターを、そのままイテラブルにしてみます。

 

イテラブルなオブジェクト

let a = {
    [Symbol.iterator] : function(){  // [Symbol.iterator]() { と記述可能
        return {
            count:0,
            next: function() {
                this.count ++;
                   return ( this.count <= 5 ) ?
                            { value: this.count * 2 , done:false }
                            : { done: true };
                }
        }
    }
};

 

[Symbol.iterator]という名前のメソッド内で、新規でイテレータを作成してリターンしているだけです。

 

簡単ですね。

 

 

イテラブルなオブジェクトをプロトタイプで実装

 

実用的にはプロトタイプで指定したほうがいいかもしれませんね。

 

実用的にはプロトタイプ

let a = function(){};
a.prototype[Symbol.iterator]=function(){
    return {
        count:0,
        next: function() {
            this.count ++;
            return (this.count <= 5) ?
                        {value:this.count * 2 , done:false }
                        : {done:true};
        }
    }
};

 

オブジェクトaを直接使うとエラーになることがあるので、newしてあげましょう。

イテラブルなオブジェクトの使い方

 

次にこのオブジェクトのどこが便利なのか、見ていきましょう。

 

for...of 文で値を取り出す

 

for of文を使うと、イテラブルなオブジェクトから値を一つ一つ取り出してくれます。

 

for of文で列挙

for( let val of a){
    console.log(val);
}

 

 

スプレッド構文でArrayに変換

 

いつまでたっても、自作オブジェクトにArrayのメソッドを使用してエラーになる僕のようなおっちょこちょいさんは多いはず。

 

JavaScriptにはスプレッド構文というものがあって、イテラブルなオブジェクトを簡単にArrayに変換できるから、これを使って解決している。

 

スプレッド構文でArrayに変換

let arr = [...a];
console.log( bb ); // Array(5) [ 2, 4, 6, 8, 10 ]

 

また次のように、スプレッド構文で引数に指定することもできる。

 

スプレッド構文で引数に指定

let max = Math.max(...a);
console.log(max); // 10

 

スプレッド構文については、次の記事を参考にしてみてください!
参考:【JavaScript】 コード中の「...」は意味があった

 

 

一つのオブジェクトに複数のイテレーターを実装する

 

Arrayオブジェクトにはkeys()という、インデックスを反復うするイテレーターを返すメソッドがあります。
メソッドで取得したイテレーターは for…of を使用できるので、イテラブルなオブジェクトともいえます。

 

Arrayオブジェクトはそのものがイテラブルなオブジェクトですが、その他にもイテラブルなオブジェクトを提供しているということです。

 

そこで自作のオブジェクトに、複数のイテラブルなオブジェクトをサポートしてみます。

 

オブジェクトプロパティ値を列挙

 

まずはオブジェクトプロパティ値を列挙してみます。

 

let a = function( obj ){
    this.obj = obj;
};
a.prototype[Symbol.iterator] = function () { // イテラブルなオブジェクト
    return {
        count: -1,
        value: Object.values( this.obj ),
        next: function() {
            return a.prototype.iterableNext.call( this );
        }
    }
};
a.prototype.iterableNext = function(){
    this.count ++;
    return (this.count < this.value.length) ?
                {value:this.value[this.count] , done:false }
                : {done:true};
};
let b = new a({data1:"a",data2:"b",data3:"c"});
for( let val of b){
    console.log( val ); // 結果: "a" "b" "c"
}

 

内部的に Object.values() を使っていて、全く実用的ではありませね…
とりあえず例ということで気にしないでいきます。

 

next()内のコードは、共通的に使用したかったので別関数にしてあります。

 

オブジェクトのキーを列挙

 

次に、オブジェクトのキーに対するイテラブルなオブジェクトを取得するメソッドを作成してみます。

 

a.prototype.keys = function(){
    return {
        obj:this.obj, // objへの参照を確保しておく
        [Symbol.iterator]:function(){
            return{
                count:-1,
                value:Object.keys(this.obj),
                next: function() {
                    return a.prototype.iterableNext.call(this);
                }
            };
        }
    };
};
for( let val of b.keys()){
    console.log( val ); // 結果:"data1" "data2" "data3"
}

 

[Symbol.iterator]を持つオブジェクトを作成して返すオブジェクトを、返すメソッドを定義しています。
このとき、a.prototype.keysから返されたオブジェクトは、オブジェクトaのthisを参照できません。

 

そのため、あらかじめobjの参照を確保しています。

 

ジェネレーターを使ってみる

 

最後に、これまでのコードをジェネレーターに書き換えてみます。
ジェネレーターについては、次の記事で解説しているので確認してみてください。

 

参考:【JavaScript】 Generator(ジェネレーター)ってなんだ?関数とは何が違うのか

 

let a = function(obj){
    this.obj = obj;
};
a.prototype[Symbol.iterator]=function* (){
    yield* Object.values(this.obj);
};
a.prototype.keys = function(){
    return {
        obj:this.obj,
        [Symbol.iterator]:function* (){
                yield* Object.keys(this.obj);
        }
    };
};

 

let b = new a({data1:"a",data2:"b",data3:"c"});
for( let val of b){
    console.log( val );  // 結果: "a" "b" "c" 
}
for( let val of b.keys()){
    console.log( val ); // 結果:"data1" "data2" "data3"
}

 

めちゃくちゃコードが短くなりました。
鼻血出そうですね!!

 

まとめ

僕の場合、今回の例のような取得時にデータ生成されるものを列挙するには、一度配列に格納してからforEachを利用していました。

 

今後は自作のイテレーターが活躍しそうですね。

スポンサーリンク

記事の内容について

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

OFUSEを設置してみました。
応援いただけると嬉しいです。