ActionScriptハマりどころ インスタンス変数初期化編

今日も元気にActionScript2.0を書いていたのですが、大いにハマるところがありました。次の何の変哲もないクラスを定義したとします。

class Foo{
  var bar = [];
  function Foo(n){
    bar.push(n);
  }
}

単に、Fooというクラスがbarという配列を持っており、コンストラクタに与えられた引数をその配列に追加する。で、次のように使う。

a = new Foo(1);
b = new Foo(2);
trace(a.bar);
trace(b.bar);

するとどうでしょう。a.barもb.barも中身は[1,2]となります。これはクラスベースのオブジェクト指向言語としてはちょっと一般的ではありません。barはインスタンス変数だと思っていたのに、これではまるでクラス変数!

別の言語で試してみましょう。例えばPHP

class Foo{
  var $bar = array();
  function Foo($n){
    array_push($this->bar,$n);
  }
}
$a = new Foo(1);
$b = new Foo(2);
print_r( $a->bar);
print_r( $b->bar);

で、出力結果。

Array
(
    [0] => 1
)
Array
(
    [0] => 2
)

正しく別の配列になっています。期待していたインスタンス変数の振る舞いです。

どうしてASではこれがすんなりいかないのか。1時間くらい悩んでしまいましたが、結論から言うとインスタンス変数の初期化の位置がまずかったのでした。

class Foo{
  var bar;
  function Foo(n){
    bar = [];
    bar.push(n);
  }
}

これなら大丈夫。要は、最初のコードでは、クラス定義の段階で配列オブジェクトが生成されてしまい、それが全てのインスタンスのbarから参照され続けてしまうのです。従って、AS2.0はインスタンス変数がオブジェクトである場合、その初期化は変数定義に書いてはならず、コンストラクタなどで行わなければなりません!

これはAS2.0が微妙にJavaScriptに近いために起こってしまった悲劇だと思います。AS3.0では確認していませんが、こういうことは起こらないのではないかと想像しています。