この方法ではArrayを継承することは… できないッ!

「まるごとJavaScript & Ajax ! Vol.1」買いました。

まるごとJavaScript & Ajax ! Vol.1

まるごとJavaScript & Ajax ! Vol.1

えーと、最初にartonさんのJavaScriptの落とし穴があって、その次が弾さんプロタイプベースオブジェクト指向の解説(404 Blog Not Found:javascript - プロトタイプ的継承とか)があって、その次がid:amachangPrototype.jsの解説で、その先が…でとにかく盛りだくさんですね!




あっ。

弾さん、それまずい。

前から気づいてたんですけど、その方法じゃArrayを継承することはできないです。

継承されたArrayがうまく動かない例
function myArray(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

var proto = [0, 1, 2];
var myary = myArray(proto);
alert(myary.length + ' : ' + myary.join(','));
myary.push(3);
alert(myary.length + ' : ' + myary.join(','));  // IEでは追加した要素が表示されない。
myary[4] = 4;
alert(myary.length + ' : ' + myary.join(','));  // IE, Firefoxともに追加した要素が表示されない。
alert(myary.pop());                             // IEでは2, Firefoxでは3が表示される。

Firefoxでも、IEでも期待通りに動いてはくれません。
myaryはブラケット([ ])で要素を追加したときにlengthが更新されないようです。

じゃあ、どうやってArrayを継承すればいいの?うーむ…わかりません。

追記(2/22)

id:delive1さんがブクマコメントで、

Arrayとかの組込みObjectはvalueOf、toStringをイジるのだけでいいかと

http://b.hatena.ne.jp/dlive1/20070222#bookmark-4029980

とおっしゃられている。「JavaScript継承パターンまとめ - Thousand Years」のリンクを書いてくれたid:trickstar_osさんも同じことを言いたいのだと思います。
しかし、その方法でうまくいくのはStringやNumberの各メソッドが内部的にtoStringやvalueOfを呼び出しているからです。Arrayの場合はそれに該当するような根幹的なメソッド*1は存在しないのでこの方法は、残念ながらまったく解決になりません。
問題の本質は rubyでいうところの []= をユーザが定義できないことです。これを解決するにはjavascript2.0を待つ以外には方法は無いのでは無いかと思います。

自分が試した中で一番まともに動いたのはこんな感じです。

// Firefox Only
function MyArray(strage) {
   strage = Array.apply(null, strage) || [];
   this.__noSuchMethod__ = function(name, args) {
     strage[name].apply(strage, args);
   }
   this.toString = function() {return strage.toString();}
   this.__defineGetter__("length" , function() {return strage.length;});
   this.__defineSetter__("length" , function(l) {return strage.length =l;});
   this.get = function(i) { return strage[i]; };
   this.set = function(i, v) { return strage[i] = v; };
}
ary = new MyArray([1,2,3]);
alert(ary.length + ' : ' + ary);
ary.push(4);
alert(ary.length + ' : ' + ary);
ary.set(0, 0);
alert(ary.length + ' : ' + ary);
ary.set(10, 10);
alert(ary.length + ' : ' + ary);

Firefoxオンリーですがポイントは以下の通り

  • []を使うのをあきらめてgetとsetを使う(javaみたいだ)
  • __noSuchMethod__でメソッド呼び出しをArrayに委譲する(継承じゃ無いじゃん)
  • __defineGetter__と__defineSetterでlengthの読み書きをArrayに委譲する

実際のところは、「Arrayをどーしても継承したい」なんて人はいないと思うので全然問題ないのですが。

*1:rubyのArrayならeach、javaのAbstractCollection継承クラスならiteratorが該当する