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)」で記述した「返却された関数が返却元関数の変数を使用できる」と同じ理屈です。詳しくはそちらを参照してください。