再入門JavaScript: For文とイテレータ

ES2015 [6th edition] で letconst が導入された。var と書いてあるところにはこれらも書ける。

繰り返し構文

JavaScript の forは、次の2つの書き方がある。C/C++風の書き方と、in/of演算子を使った書き方。

コンテナの要素を単純に使いたいときは後者を使う。

  1. for (式1; 式2; 式3) ...
    for (var宣言; 式2; 式3) ...
  2. for (要素 in コンテナ) ...
    for (要素 of コンテナ) ...

for

1.変数の代入, 2.ループするかどうかの条件式, 3.変数の更新, の3つの引数を取る。2番目の条件式が真の間、ループされる。

下, 2番目の例のように, var宣言で関数スコープ (forブロックではない) の変数を宣言できる。現代は let を使うのが通常.

JavaScript
[RAW]
  1. function f() {
  2. for (i = 0; i < 3; i++)
  3. document.write(i, ' '); // 0 1 2
  4. }
  5. f();
  6. document.write(i, '<br />'); // 3
JavaScript
[RAW]
  1. j = 2;
  2. function g() {
  3. for (var j = 10; j < 15; j += 2)
  4. document.write(j, ' '); // 10 12 14
  5. document.write(j, ' '); // 16
  6. }
  7. g();
  8. document.write(j, '<br />'); // 2

for-in文と for-of文

ES5 (2009年) にも for-in文があった。ES2015 (ES6) で, for-in文に加えて for-of文が導入された。書き方は非常に似ている。

in はキーワードだが, of はそうではない。文脈依存キーワードになっている。

for-in文
for ( 左辺値 in 式 ) 文
for ( var ForBinding in 式 ) 文
for ( ForDeclaration in 式 ) 文
for-of文
for ( 左辺値 of AssignmentExpression ) 文
for ( var ForBinding of AssignmentExpression ) 文
for ( ForDeclaration of AssignmentExpression ) 文

for-in文

for-in文は、オブジェクトの全てのプロパティのうち [[Enumerable]] = true 属性を持つものを順に変数に代入し、それで文を実行する。

順序が先頭からとは限らない。余計なものが付く、という難点があり、実用的でない。次の例は配列だが、配列の要素ではなくインデックスになっているし、fn も渡されている。

JavaScript
[RAW]
  1. let ary = [1, 3, 5];
  2. ary.fn = function() { document.write('hoge'); }
  3. for (let v in ary)
  4. document.write(v, ' '); // #=> 0 1 2 fn

in の後ろにオブジェクトを書いた場合は、key が順番に渡されてくる。

いずれにしても、新しいプログラムを書く際は, もはや for-in文は使うべきではない。

for-of文

for-of文は, for文に渡されたオブジェクトが「反復可能 (iterable) オブジェクト」であるとき, そのオブジェクトの @@iterator プロパティを呼び出してイテレータ (後述) を得て, そのイテレータでループを回す。逆に, @@iterator プロパティを持つオブジェクトが iterable である。

配列 Array の場合は、ちゃんと配列の要素が順番に得られる。文字列 String だとそれぞれの code point. Map だと entries() の各要素, Set だと values() の各要素.

ただのハッシュオブジェクトだと、イテレータがないので, TypeError 例外になる。

新しいプログラムでは, for-of文のほうを使うようにすればいい。

Iterableオブジェクトと iterator

配列を取りそうな場所は、配列決め打ちではなく, iterable オブジェクトであれば受け付けるようにすると汎用性が上がる。

イテレータを生成できるオブジェクトを iterable オブジェクトと呼ぶ。

イテレータは少なくとも next プロパティとして関数を持たなければならない。returnプロパティ (関数), throwプロパティ (関数) を持ってもよい。next() が返すオブジェクトは done, value のプロパティを持たなければならないき。

自分でイテレータを作るには、次のようにする;

JavaScript
[RAW]
  1. // イテレータは、少なくとも nextメソッドを持つこと.
  2. const iter = {
  3. count: 10,
  4. // @return オブジェクト. value, doneプロパティを持つ.
  5. next: function() {
  6. this.count++;
  7. // .done === true の場合, .value は捨てられる.
  8. return {value:this.count, done:this.count === 15};
  9. }
  10. };
  11. // この状態は iterable ではない.
  12. const obj = { a:1 };
  13. // @@iterator プロパティが呼び出されたら, イテレータを返すようにする.
  14. obj[Symbol.iterator] = function() { return iter };
  15. // x of obj の場合, イテレータを使う.
  16. for (let x of obj) {
  17. console.log(x); // #=> 11 12 13 14
  18. }

上の例では iterator を obj とは別のオブジェクトにしたが、obj のプロパティとして next を定義すれば、iterable オブジェクトかつ iterator にすることもできる。