Hashクラス

【抜粋】一部省略
var Hash = {
  _each: function(iterator) {
    for (key in this) {
      var value = this[key];
      if (typeof value == 'function') continue;

      var pair = [key, value];
      pair.key = key;
      pair.value = value;
      iterator(pair);
    }
  },
(省略)
  inspect: function() {
    return '#<Hash:{' + this.map(function(pair) {
      return pair.map(Object.inspect).join(': ');
    }).join(', ') + '}>';
  }
}

いわゆる連想配列を扱うクラスです。Hashクラスも、Enumerableクラスと同様、そのインスタンスが作成されることはありません。抽象クラス扱いです。ただ、実装方法は少し特殊です。他クラスに継承させるのではなく、$H関数によって返却されるオブジェクトがHashクラスを継承している、という形になります。Hashオブジェクトを作って各プロパティを追加するよりも、カスタムオブジェクトを作ってそれにHashを継承させたほうが手っ取り早い、ということなのでしょう。(2006/08/03追記。new Hash({name:value,・・・}); という手もあった気はしますが。メソッドを上書きされるのを恐れたのでしょうか。。。→2007/10/01追記 v1.5.〜ではこの方法が採られています。)

【例】
var hash = $H({zero:"零", one:"壱",two:"弐", three:"参"});
//hashはHashクラスを継承したオブジェクト。

$H関数の制限

※ここで言及している制限は、最新(v1.6.0以降)では改善されています。「v1.6.0 Hashクラス - Backstage of theater.js」を参照してください。

先に書いてしまいますが、$H関数に渡すことのできるカスタムオブジェクトには、以下の制限があります。

  • すでに実装されているメソッドと同じ名前のプロパティは作れない。

Enumerableクラス、Hashクラスが持つメソッドと同じ名前のプロパティは作れません。具体的には以下。

【参考】$H関数に渡すカスタムオブジェクトで使えないプロパティ名
each,all,any,collect,detect,findAll,grep,include,inject,invoke,
max,min,partition,pluck,reject,sortBy,toArray,zip,inspect,map,
find,select,member,entries,_each,keys,values,merge,toQueryString

使うと、返却されるオブジェクトからそのプロパティが消えます。(実際にはメソッドで上書きされている)

【参考】
var hash = $H({zero:"零", one:"壱", two:"弐", inspect:"文字列化", three:"参"});
alert(Object.inspect(hash));
//#<Hash:{'zero': '零', 'one': '壱', 'two': '弐', 'three': '参'}> と表示される。

inspectというプロパティが消えました。inspectメソッドは機能しています。

  • 値として関数はとれない。

関数はメソッドとみなされ、各メソッドの処理対象となりません。

【参考】
var hash = $H({zero:"零", one:"壱", two:"弐",
               func:function(){alert("!");},
               three:"参"});
alert(Object.inspect(hash));
//#<Hash:{'zero': '零', 'one': '壱', 'two': '弐', 'three': '参'}> と表示される。
hash.func();
//"!"と表示される

funcメソッドは作成されますが、inspectの処理対象からは除外されています。

んー、後者はいいとして、前者は危険ですね・・・。うっかり使ってしまっても、気がつかないかもしれませんし。。。

2006/11/06追記。「prototype.jsのHashクラスの制約を回避 - Backstage of theater.js」に改修案を記述しました。

_eachメソッド

【抜粋】
  _each: function(iterator) {
    for (var key in this) {
      var value = this[key];
      if (typeof value == 'function') continue;

      var pair = [key, value];
      pair.key = key;
      pair.value = value;
      iterator(pair);
    }
  },

Enumerableクラスを継承する場合に必要なメソッド。自オブジェクトの各プロパティのプロパティ名と値を格納したオブジェクトを、引数の関数iteratorに渡して実行します。これにより、Enumerableから継承したメソッドで各プロパティを処理できます。

for inループにより自オブジェクトthisのすべてのプロパティ・メソッドを処理しています。値valueが関数(メソッド)であれば処理しません。pairに、プロパティ名keyと値valueを順に格納した配列を作成しています。更に、pairのkeyプロパティにプロパティ名key、valueプロパティに値valueを作成・格納しています。つまり、iterator内では、プロパティ名ならpair[0]とpair.key、値ならpair[1]とpair.valueのどちらでもアクセスできることになります。

【参考】
var hash = $H({zero:"零", one:"壱", two:"弐", three:"参"});
hash.each(function(pair){
  alert(pair[0] + "," + pair[1] + "," + pair.key + "," + pair.value);
  //"zero,零,zero,零"・・・と表示される
});

keysメソッド

【抜粋】
  keys: function() {
    return this.pluck('key');
  },

前述Enumerableクラスから継承したpluckメソッドにより、自オブジェクトのプロパティ名のみの配列を返却します。pluckが処理するのは前述_eachメソッド内で作成されるpairオブジェクトです。自オブジェクトthisそのものではないので注意してください。

【例】
var hash = $H({zero:"零", one:"壱", two:"弐", three:"参"});
var ret = hash.keys();
alert(ret);
//retは配列。"zero,one,two,three"と表示される。

valuesメソッド

【抜粋】
  values: function() {
    return this.pluck('value');
  },

こちらは自オブジェクトの値のみの配列を返却します。

【例】
var hash = $H({zero:"零", one:"壱", two:"弐", three:"参"});
var ret = hash.values();
alert(ret);
//retは配列。"零,壱,弐,参"と表示される。

mergeメソッド

【抜粋】
  merge: function(hash) {
    return $H(hash).inject($H(this), function(mergedHash, pair) {
      mergedHash[pair.key] = pair.value;
      return mergedHash;
    });
  },

自オブジェクトthisに引数hashをマージするメソッドです。引数hashの各プロパティを、injectメソッドにより、自オブジェクトthisの同一プロパティ名へ設定しています。よって、重複するプロパティ名があった場合、追加するhashのプロパティに上書きされます。

引数hashを$H関数で処理しているので、hashは単なるカスタムオブジェクトでもいいようです。ただ、なんでthisまで$H関数で処理しているのかは分からないのですが。。。→2006/08/08修正。thisの複製を作成し、thisを破壊しないようにするためですね。(同じ間違いを・・・orz)

【例】
var org = $H({zero:"零", one:"壱", two:"弐"});
var add = {two:"二", three:"参"}; //$Hで処理しなくても可
var ret = org.merge(add);
alert(Object.inspect(ret));
//retはHashオブジェクト(を継承)。
//#<Hash:{'zero': '零', 'one': '壱', 'two': '二', 'three': '参'}> と表示される。

toQueryStringメソッド

【抜粋】
  toQueryString: function() {
    return this.map(function(pair) {
      return pair.map(encodeURIComponent).join('=');
    }).join('&');
  },

自オブジェクトthisの各プロパティを、既存encodeURIComponent関数によりURIエンコードし、プロパティ名と値を「=」で連結した文字列にします。その上で各文字列を「&」で連結し、その文字列を返却します。

【例】
var hash = $H({zero:"零", one:"壱", two:"弐", three:"参"});
alert(hash.toQueryString());
//"zero=%E9%9B%B6&one=%E5%A3%B1&two=%E5%BC%90&three=%E5%8F%82"と表示される。

ところで、pairはArrayクラスのオブジェクトであるため、そのmapメソッドが処理するのはpair[0]とpair[1]となり、pair.keyとpair.valueは処理されません。

inspectメソッド

【抜粋】
  inspect: function() {
    return '#<Hash:{' + this.map(function(pair) {
      return pair.map(Object.inspect).join(': ');
    }).join(', ') + '}>';
  }

Hashを文字列化するメソッド。Object.inspectメソッド経由で使用されることが前提です。・・・このくらいの処理ならもう説明は不要でしょうか^^; 例についても、前述の他のメソッドにあるので割愛します。