イテレータについてはHawk's Laboratory » JavaScript 1.7の新機能が詳しいけれども、一応自分用のメモとして残しておく。
Javascriptにおけるイテレータは抽象クラス様のオブジェクトであり、nextメソッドを持つことだけが要求される。ちなみにJavascriptといったらここではJavascript1.7(以下略)。
次のようなコードを考える:
for each (let 変数 in オブジェクト){}
for (let 変数 in オブジェクト) {}
for each文、for in文におけるループでは、そのオブジェクトの__iterator__
プロパティが(あれば)callし、それをイテレータとして利用する。ループ毎にそのnext
メソッドがcallされその戻り値が変数に格納される。next
メソッドがStopiteration
例外を投げると暗黙的にcatch
しそして自動的にループが終了する。ちなみにvar
演算子ではなくlet
演算子で変数を宣言すると、for each, for inブロック中の局所変数とすることができる。
イテレータのエッセンスを紹介する例として、for each(let i in new Range(10)){}
という文でブロック内の処理を10回繰り返す、という「Rangeオブジェクト」を定義してみよう。
コンストラクタ |
|
---|---|
__iterator__プロパティ |
|
使用例 |
|
Rangeオブジェクトの__iterator__()
が返すイテレータは、1) next
メソッドを持っていて、2) 一定回数以上呼び出されるとStopIteration
を投げて自身を初期化する。この2点を満たしていれば何でも良い。したがって上の例はジェネレータを使った方が明らかにベターなのだが、構造が隠蔽されてしまうので説明には不向きであった。他には自分自身をイテレータにしてしまう方法もあり、それぞれに利点と欠点がある。
|
ジェネレータはnextメソッドを持ち、ループが終わると自動でStopIterationを投げてくれるので、ジェネレータを使えば最も簡潔に書けるが、ここではジェネレータの特性についてこれ以上の説明はしない。
定義 |
|
---|---|
使用例 |
|
自身をイテレータにする利点は、自分のメソッドとしてnext
メソッドを明示的に使用できることである。
上のRange
オブジェクトを使ったループはfor in文でも全く同様に動作する。即ち上のような__iterator__
プロパティはfor each文とfor in文のセマンティクスの違いを吸収するという副作用を持っている。この点には十分に注意して使う必要があるだろう。for each文とfor in文でセマンティクスを変えたいなら、__iterator__
がcallされる際、for each文ではfalse、for in文ではtrueが引数として渡されるのを利用すれば良い。
さらに__iterator__
は、そのオブジェクトのプロパティと値のセット(リスト)を列挙してくれるIterator
関数さえ「上書き」してしまう。しかしIterator
関数の第二引数にtrue
を指定すればプロパティ名のみを列挙する組み込みのイテレータが返される。
繰り返すと、__iterator__
プロパティを定義した先述のRange
オブジェクトに関して、次の3つは全て同じ挙動をする:
for each(let i in new Range(10)){}
for (let i in new Range(10)){}
for each(let i in Iterator(new Range(10))){}
for in(let i in Iterator(new Range(10), true)){}
NamedNodeMapというのは属性コレクションなどのインターフェイスで、簡単な例ではdocument.body.attributesなどが挙げられる。例えばbody要素の全属性の名前と値を列挙したいとき、次のように書けるよう、イテレータを利用してみよう:
|
Python風のlet [name, value]
という書き方(Destructuring Assignment)はJavascript1.7の特権である。一見大したことはないのだが、何故かこれができるとできないとではコードの生産性が思った以上に違ってくる。
しなければならないのはたった一つ。NamedNodeMap.prototype
に__iterator__
を追加してやればいい。
|
for in文でこれがcallされたときのみnodeOnlyがtrue
なので、次の例は属性オブジェクトが列挙される:
|
自分自身をイテレータにする例については、Javascript1.7のカスタムイテレータとXPath (agenda)の_NodeListオブジェクトが適当な例だろう。ノードリスト以外の別の列挙体に基づいた処理をした後(nextメソッドを使う)、残りのノードをfor in文で列挙する、といった柔軟な処理を簡潔に記述できる。
|
命題列挙。
next
メソッドを持たねばならない。
__iterator__
をcallした結果がイテレータとして利用され、毎ループ時そのnext
メソッドが呼ばれる。
next
メソッドからStopIteration
例外を受け取るとfor in/for eachループは終了する。
__iterator__
を呼ぶ際にtrue
が第一引数として渡される。
__iterator__
が定義されていない場合、デフォルトのイテレータが使用される。デフォルトではfor in文ではプロパティ名の列挙、for each文では値の列挙が行われる。
Iterator
関数は、デフォルトではプロパティ名と値のリストを列挙するイテレータを返す。
__iterator__
が定義されていると、for each文とIterator関数が使用するイテレータは同一のものとなり、使い分ける術がなくなる。
__iterator__
を定義するなら、__iterator__自身をジェネレータにしてしまうのが最も簡潔な方法であり、一般にnext
メソッド、StopIteration例外のthrowを明示する必要さえない。
__iterator__
が自分自身を返す、即ち自分自身がイテレータとなるオブジェクトを定義することもでき、柔軟な運用が可能になる。
Javascriptで「クロスブラウザ」なんてことができると思うのは誤り。こてこての「クロスブラウザ」なスクリプトも、このJavascript1.7なスクリプトも、実は互換性という意味では大差がない。互換性を高めたければ、Javascriptなしでも利用可能にしておくこと。これに尽きる。