2013年12月26日木曜日

enchant.js でもインスタンスがどのクラスから作られたか知りたい

enchant.js では JavaScript の一風変わったオブジェクトシステムの上にクラスの仕組みを実装しています。これはこれで便利なのですが、表題に書いた点は不満です。

JavaScript の処理系は、インスタンスがどのコンストラクタから作られたかを表示する機能があります。例えば、次のようなコンストラクタ Dog と Cat を宣言し、それぞれのインスタンスを作り、変数 dog と cat に代入します。

function Dog () {}
function Cat () {}
var dog = new Dog();
var cat = new Cat();

変数 dog と cat をコンソールで見ると、次のようにコンストラクタ名が表示されています。

enchant.js でクラスとして作ると、こうはなりません。同じようにクラス(のコンストラクタ)を作り、それらのインスタンスを作ります。

var Dog = Class.create(Sprite, {
  // 省略
});
var Cat = Class.create(Sprite, {
  // 省略
});
var dog = new Dog();
var cat = new Cat();

コンソールで見ると、(Google Chrome の場合)Constructor と表示され、どんなオブジェクトなのか判然としません。Safari では、Object と表示されるので、これまた判然としません。

ここはやはり、最初に見たように表示してもらえる方が、デバッグの時は助かります。特に、配列などのデータの中にしまった時の差は顕著です。

当座の解決策

結構根が深い問題で、JavaScript の限界とも言えます。

enchant.js の enchant.Class.create メソッドを次のように(赤字の部分を)書き足します。

enchant.Class.create = function(superclass, definition, constructor) {
    // 省略
    var Constructor = typeof constructor === 'function' ? constructor :
      function () {
          if (this instanceof Constructor) {
              Constructor.prototype.initialize.apply(this, arguments);
          } else {
              return new Constructor();
          }
      };
    // 省略
    return Constructor;
};

コンストラクタが匿名関数であると、このような現象が発生します。関数は後から名前を変えることができません。そこで、名前を付けた関数を作ってそれを引数として渡せるように、引数 constructor を追加します。constructor に関数が渡されたときはそれを使い、それでない場合は、従前通りに動作するように作ります。

使い方は、次のようになります。

var Dog = Class.create(Sprite, {
  // 省略
}, function Dog () {
  Dog.prototype.initialize.apply(this, arguments);
});
var Cat = Class.create(Sprite, {
  // 省略
}, function Cat () {
  if (this instanceof Cat) {
    Cat.prototype.initialize.apply(this, arguments);
  } else {
    return new Cat();
  }
});
var dog = new Dog();
var cat = new Cat();

一番最初の例と同様に、どのコンストラクタから作られたかが表示されるようになりました。

第三引数に、enchant.Class.create のはらわたの一部が出てしまっていて見苦しいのですが、この辺が限界のように思います。enchant.js が作るコンストラクタでは、initialize メソッドを呼んでいるので、それを手で書いてあげます。オリジナルのコンストラクタでは、else 句の方に new を付け忘れた場合の救済措置ではないかと推察されるコードがあります(違うかも)。Dog の例のように、その部分を省略しても動きます。心配性な人は、Cat の方のように全部書いてもいいでしょう。

enchant.js で定義されている他のクラスも、同じように書き換えれば、ちゃんとコンストラクタの名前が表示されるようになります。例えば、Sprite を書き換えるならば、次のようにします。

enchant.Sprite = enchant.Class.create(enchant.Entity, {
  // 省略
}, function Sprite () {
  enchant.Sprite.prototype.initialize.apply(this, arguments);
});

あんまりきれいではないですが、一応これでデバッグの苦痛が少し緩和されます。もっとこうした方がいい、などのコメントをお待ちしています。

0 件のコメント:

コメントを投稿