Enumerableクラス(1)

prototype.js解読の山場のひとつです。

【抜粋 一部省略】
var Enumerable = {
  each: function(iterator) {
    var index = 0;
    try {
      this._each(function(value) {
        try {
          iterator(value, index++);
        } catch (e) {
          if (e != $continue) throw e;
        }
      });
    } catch (e) {
      if (e != $break) throw e;
    }
  },
(省略)
  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
}

Enumerableクラス、と表現していますが、これ自体のインスタンスは作成されることはありません。(元々、functionではないので作成できませんが) このクラスは他の列挙可能なクラス・オブジェクト(Array、Hash、ObjectRange、Ajax.Responders、Element.ClassNames)により継承されることで実装されます。Javaでいう抽象クラスのようなものです。

Ruby言語を参考にしているらしいのですが、筆者はRubyを知らないのでそのまま解読します^^;

eachメソッド

【抜粋】
  each: function(iterator) {
    var index = 0;
    try {
      this._each(function(value) {
        try {
          iterator(value, index++);
        } catch (e) {
          if (e != $continue) throw e;
        }
      });
    } catch (e) {
      if (e != $break) throw e;
    }
  },

メソッド_eachに無名内部関数を渡しています。メソッド_eachは、Enumerableクラスを継承するクラス・オブジェクト側で実装する必要があります(抽象メソッド扱い)。

無名関数の主な処理は、引数で渡された関数(iterator)*1を実行するだけです。valueは列挙可能なオブジェクト(this)の各要素の値となります。これは_eachメソッド内で無名関数に渡されます。indexは実行毎にインクリメントされます。

無名関数は、関数iterator内でのエラーをcatchし、エラーオブジェクトが前述$continueオブジェクトであれば無視、そうでなければthrowします。更にeachメソッドが_eachメソッドのエラーをcatchし、エラーオブジェクトが前述$breakオブジェクトであれば無視、そうでなければthrowします。

【例】
var array = ["zero", "one", "two", "three"];
array.each(function(value, index){
  alert(index + ":" + value);
}); 
//"0:zero","1:one", "2:two", "3:three"が順番に表示される。

動作を追っていくと以下のようになります。

1、2の後、3と4が繰り返されています。これを_eachにまとめてみます。

【参考】
Object.extend(Array.prototype, {
  _each: function() {
    var index = 0;
    for (var i = 0; i < this.length; i++){
      try {
        try {
          alert(index + ":" + this[i]);
          index++;
        } catch (e) {
          if (e != $continue) throw e;
        }
      } catch (e) {
        if (e != $break) throw e;
      }
    }
  }
});
var array = ["zero", "one", "two", "three"];
array._each();
//"0:zero","1:one", "2:two", "3:three"が順番に表示される。

あらためて上記コードを見てみると、「alert(index + ":" + this[i]);」以外の部分は、他の繰り返し処理に流用が可能です。これを実現したのがeachメソッドといえます。

eachメソッドは以降のEnumerableクラスのメソッドで利用されています。

内部関数(クロージャ)のスコープの特性

引数として渡された内部関数(クロージャ)は、元のメソッドの変数(index)を使うことができます。前述「Functionクラスに対する拡張(1)」で記述した「返却された関数が返却元関数の変数を使用できる」と同じ理屈です。詳しくはそちらを参照してください。

*1:JavaIteratorとは別物