Flash MXのスコープチェーンを活用する

パラメータ化されたfunction、staticプロパティ・メソッドとprivateプロパティ

- Timothee Groleau著 -

上野直彦、北沢 純(FACEs Project)訳

2003年4月9日初版(元記事)公開

導入

前の記事で、Flash MXでネストしたfunctionを使うことはメモリの浪費につながる可能性があるということを見てきた。これは真実ではあるが、ネストしたfunctionとそのアクティベーションオブジェクトの持続という振る舞いを利用することで可能になることもたくさんある。この記事では、パラメータ化されたfunctionと、Object.addPropertyの拡張バージョン、ActionScript OOPにおけるstaticなプロパティとメソッド、そして最後にprivateプロパティについて論じていく。

この記事では、ActionScriptと一般的なOOPの実践についてある程度理解していることを前提とする。前の記事を読んでおけば必ず理解の助けになる。

この記事の最後では、記事中で議論したコードを含むライブラリファイルがダウンロードできる。

 

謝辞

ActionScript OOPについて私が知っていることのほとんどは、Robin DebreuilのオンラインブックとDave YangのQuantumwaveのFlashセクションを読んで知ったことだ。私が最初にActionScriptのprivateプロパティについて読んだのはFlashCoders MLにおける加藤達夫のこの投稿である。

 

パラメータ化されたfunction

ステージ上にたくさんのムービークリップがあってそれらを全部一律にランダムな方向に動かし(各ムービークリップは線をなぞる)ステージ上でループさせたいと思っていると想像しよう。何らかの理由で、その振る舞い方を扱うためのサブクラスを作成したくないのだが、その代わりに自分のコードを一箇所に集中させてムービークリップ全てをそこで初期化する必要があるとしよう。

一般的には、ムービークリップを独立に動かすには、各フレームでそれぞれのムービーを少しずつ(xとy軸両方)動かす必要がある。onEnterFrameはパラメータ無しでブロードキャストされるので、各ムービークリップが独立に動くように別のパラメータを渡したいときにイベントを使うことはできない。よって各ムービークリップ内においてxStepとyStepという変数を格納しonEnterFrameハンドラがそれらの値に基づいて行動を起こすようにする必要がある。一般的には、コードはこんな感じになる(ムービークリップが10個の想定):

コード:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
move = function() {
  // mcのプロパティを使う
  this._x += this.xStep;
  this._y += this.yStep;

  if (this.xStep > 0) {
    if (this._x > Stage.width) this._x = 0;
  } else {
    if (this._x < 0) this._x = Stage.width;
  }
  if (this.yStep > 0) {
    if (this._y > Stage.height) this._y = 0;
  } else {
    if (this._y < 0) this._y = Stage.height;
  }
}

for (var i=0; i<10; i++) {
  o = this["mc" + i];

  // mcのプロパティを設定
  o.xStep = Math.random() * 50 - 25;
  o.yStep = Math.random() * 50 - 25;
  o.onEnterFrame = move;
}

ここでもし、各ムービークリップにそれぞれステップ値をあらかじめ格納しておきたくなかったらどうするか? 全てのムービークリップにそのムービークリップのパラメータに基づいた汎用のfunctionを割り当てたいとは思わないだろう: 代わりに、ムービークリップに対してパラメータ化されたfunctionを割り当てて、それが魔法のように望みの動作を行ってくれるのが理想のはずだ。

これはスコープチェーンを使えばできる。アクティベーションオブジェクト内にいくつかのパラメータを格納してから新規に生成されたfunctionを返すfunctionを作成することができる。この方法によってムービークリップはきれいなままに保つことができる。また、おそらく幾らかスピードがあがるだろう。なぜなら"this.xStep" へのアクセスは、スコープチェーン内で"xStep"を取得するよりも少し遅くなるからだ。

コードはこんな感じになる:

コード:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
getMoveFunction = function() {
  var xStep = Math.random() * 50 - 25;
  var yStep = Math.random() * 50 - 25;

  return function() {
    this._x += xStep;
    this._y += yStep;
    if (xStep > 0) {
      if (this._x > Stage.width) this._x = 0;
    } else {
      if (this._x < 0) this._x = Stage.width;
    }
    if (yStep > 0) {
      if (this._y > Stage.height) this._y = 0;
    } else {
      if (this._y < 0) this._y = Stage.height;
    }
  }
}

for (var i=0; i<10; i++) {
  // パラメータ化されたfunction
  this["mc" + i].onEnterFrame = getMoveFunction();
}

このような選択肢に向かって進むことに決めるなら、10個のムービークリップがそれぞれ独自のfunctionを持っていてそれらがメモリ内で場所を取っていることを覚えておこう。

パラメータ化されたonEnterFrame functionは前のコード例より少し速いが、速度の差はかなり小さいことが単純なテストで分かるので、あまり強調しないでおこう。速度に関わらず、パラメータ化されたfunctionが自分にとって有用かどうかだけを考えよう。

参考のために言うと、どちらの場合でも、スピードを上げるには、function自体の内部で条件判定するのではなく、条件に応じてあるいはxStepとyStepの値に応じて別のfunctionを使うのがよい。

上記のコードも、内側のfunctionのパラメータが外側のfunctionの中でランダムに生成されるという意味ではあまり良いとは言えない。 "getMoveFunction()"を呼び出したときにパラメータを渡せば、パラメータ化されたfunctionの振る舞い方をコントロールできるということを心に留めておこう。

 

Object.AddProperty2

Flash MXの素晴らしい機能の一つに、Object.addPropertyがある。これによって仮想プロパティを作成することが可能になり、この仮想プロパティがアクセスされたときもしくは値を割り当てられたときに、ユーザに意識させること無くgetterとsetterの関数が呼び出される。多くの場合、オブジェクトに対して仮想プロパティを発行しても オブジェクト内部に値を格納する必要があり、おそらくあなたは他の開発者にこの内部的な値を直接扱わせることは望まないだろう。

addPropertyの使用の典型的な例は、こんな感じになる(まぬけな例だが気にしないように):

コード:
1
2
3
4
5
6
7
8
9
10
11
12
13
getter = function() {
  // getter functionの使い方を示すためのダミー操作
  return Math.round(this.$internalProp * this.factor); 
}
setter = function(val) {
  this.$internalProp = val;
}

obj = {};
obj.addProperty("theProp", getter, setter);
obj.factor = 3;
obj.theProp = 4.12355;
trace(obj.theProp); // 12
出力:

12

問題は、ActionScriptがprivateなプロパティを作れないので誰でも内部プロパティ"$internalProp"を直接アクセスして仮想プロパティのgetter/setterを迂回できてしまうということだ。これは問題だ。

こんなときこそ、またスコープチェーンが助けに来てくれる! getterとsetterのfunctionにあと二つ制約を追加すれば、ネストした関数を使って、外側のfunctionのアクティベーションオブジェクトに内部プロパティを格納することができる。内側の関数のスコープチェーン以外に外側の関数のアクティベーションオブジェクトへのリンクは存在しないので、だれも内部の値にアクセスできない。

追加の制約二つとは:

  • setterは内部プロパティとして格納される値を、返さなければならない。
  • getterは内部の値をパラメータとしてとる。

理にかなっているでしょ? 以下がaddProperty2の実装コードである:

コード:
1
2
3
4
5
6
7
8
9
10
11
12
Object.prototype.addProperty2 = function(name, getter, setter) {
  var prop;
  this.addProperty(name, 
          function() {
            return getter.call(this, prop);
          },
          function(val) {
            prop = setter.call(this, val);
          }
        );
}
ASSetPropFlags(Object.prototype, "addProperty2", 1);

このコードでは、getterとsetterは"this"参照によって現在のオブジェクトを参照しているので、まだpublicなプロパティはアクセス可能だ。以下は上記のコードがFlashファイルの中にあると仮定した場合のテスト用スクリプトである:

コード:
1
2
3
4
5
6
7
8
9
10
11
12
getter = function(internalValue) {
  return Math.round(internalValue * this.factor);
}
setter = function(val) {
  return val;
}

obj = {};
obj.addProperty2("theProp", getter, setter);
obj.factor = 3;
obj.theProp = 4.12355;
trace(obj.theProp); // 12
Output:

12

上記のスクリプトでは、内部の値はスコープチェーン内に隠されており外部のスクリプトからはアクセスできない。問題なのは、隠されているのでオブジェクト自身からさえアクセスできなくなっており、結局仮想プロパティを使わざるを得ないということだ。いいのか悪いのか、自分の役に立つかどうか考えてみよう。

このコードには別の問題もある。addProperty2を使ってもクラス全体において仮想プロパティを作成できるわけではない。標準のaddPropertyを使った場合、"this"参照により内部値がインスタンス自体に格納されたとき、クラスのprototypeの中で仮想プロパティを定義しておくことで、各インスタンスが自前の内部値を持つことになるだろう。addProperty2では、値はインスタンスに固有のものではなくスコープチェーンに隠されているので、クラスのprototypeに仮想プロパティを追加すると、インスタンスのどれかがプロパティを変更したら全てのインスタンスに影響を与えるプロパティを生成することになる。

いやちょっとまて、 これはstaticプロパティの説明と似てないか? いい線だ :) もうちょっとしたらスコープチェーンを使ったstaticプロパティの生成をやってみよう。ここでは、もし本当にprivateな内部値が欲しければaddProperty2は「それぞれのインスタンス」で独立に使われなければならないので、あまり効率的でないかもしれないということを覚えておこう。これのうまい使い方を見つけられるかどうかはやはりあなた次第だ。幸運を祈る。

 

staticプロパティ

上記のちょっとしたコードは、我々がどこに向かおうとしているかについてのよい見通しを与えてくれた。しかし更に進む前に、ちょっとOOPの復習をしておこう。このセクションでは常に、"static"という言葉を、Javaの"public static"の意味で使う。

staticプロパティとは何か?

staticプロパティはインスタンスではなくクラスに属するプロパティである。これは、あるクラスにおいて、staticプロパティはそのクラスのインスタンスを作成せずにアクセスすることができるということを意味する。

Javaでは、staticプロパティはそのクラスのインスタンスからもアクセス可能である。あるインスタンスがstaticプロパティの値を変更すると、そのクラスの全てのインスタンスとそのクラス自体が変更を反映する。

インスタンスに関して、以下にstaticの振る舞いを説明するためのちょっとしたJavaコードを挙げる:

コード (JAVA):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Test
{
  public static int theInt = 5;

  public static void main(String[] args) {
    Test t1 = new Test();
    Test t2 = new Test();

    System.out.println(Test.theInt); // 5
    System.out.println(t1.theInt); // 5
    System.out.println(t2.theInt); // 5

    System.out.println("===");

    Test.theInt = 6; // クラスから修正

    System.out.println(Test.theInt); // 6
    System.out.println(t1.theInt); // 6
    System.out.println(t2.theInt); // 6

    System.out.println("===");

    t1.theInt = 7; // インスタンスから修正

    System.out.println(Test.theInt); // 7
    System.out.println(t1.theInt); // 7
    System.out.println(t2.theInt); // 7
  }
}
出力:

5
5
5
===
6
6
6
===
7
7
7

 

ActionScriptではどうだろう?

ActionScriptでは残念なことに、これに近いものがない。インスタンスからプロパティを設定しても他のインスタンスに反映されず、またプロパティはクラス自体からはアクセスできないので、プロパティをクラスのprototypeに追加してもだめなのだ。(Robin Debreuilの 説明)の説明を参照。プロパティはクラス自体からはアクセスできない。

多くの人がやっているのは、クラス自体にプロパティを追加することでプロパティが一箇所にしか存在しないことを保証するというものだ。そのあと、メソッドからプロパティをアクセスするために、ハードコードされたクラスへの参照が作られるか、インスタンスからプロパティから"this.constructor.theProperty"を使ってアクセスされるかどちらかになる。残念なことに、これではJavaの様な柔軟性がなく、staticプロパティがスーパークラスに属していたら継承を行う場合に破綻する。

もう一度、スコープチェーンは便利だ。スコープチェーンによって、プロパティを生成するための一意で安全な場所を確保できる。そこで、クラスとクラスのprototype両方に対して仮想プロパティを発行すると、罠にはまる。以下のコードそのあとのテストケースをチェックしてほしい:

コード:
1
2
3
4
5
6
7
8
9
10
11
12
Function.prototype.addStaticProperty = function(name, prop) {
  var getter = function() {
    return prop;
  }
  var setter = function(val) {
    prop = val;
  }

  this.addProperty(name, getter, setter);
  this.prototype.addProperty(name, getter, setter);
}
ASSetPropFlags(Function.prototype, "addStaticProperty", 1);

テストケース(上記コードがムービーに存在すると仮定):

コード:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Test = function() {}
Test.addStaticProperty("theInt", 5);

t1 = new Test();
t2 = new Test();

trace(Test.theInt); // 5
trace(t1.theInt); // 5
trace(t2.theInt); // 5

trace("===");

Test.theInt = 6;

trace(Test.theInt); // 6
trace(t1.theInt); // 6
trace(t2.theInt); // 6

trace("===");

t1.theInt = 7;

trace(Test.theInt); // 7
trace(t1.theInt); // 7
trace(t2.theInt); // 7
出力:

5
5
5
===
6
6
6
===
7
7
7

つまりaddStaticPropertyを使うことで、Javaと振る舞いが同じになり、ご覧のとおり汚染も起こらない: 内部の値はアクティベーションオブジェクトの中に保持されどこからもアクセスできない。もう一つよいところは、アクティベーションオブジェクトがクラス全体で一つしか存在しないのでメモリ使用効率が悪くないということだ。

もちろんJavaと全く同じように動くというのは虫のよすぎるはなしで、残念ながら継承に関してまだ一つ問題がある。staticプロパティはサブクラスのインスタンスからアクセス可能ではあるが、サブクラス自体からはアクセスできない。これは厄介だ。以下のコードで実証する:

コード:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SuperTest = function() {};
SuperTest.addStaticProperty("prop", 5);

Test = function() {};
Test.prototype = new SuperTest();

t1 = new Test();
t2 = new Test();

trace(t1.prop); // 5
trace(t2.prop); // 5

trace("===");

t1.prop = 6;

trace(t1.prop); // 6
trace(t2.prop); // 6

trace("===");

trace(SuperTest.prop); // 6
trace(Test.prop); // undefined
出力:

5
5
===
6
6
===
6
undefined

23行目はstaticプロパティがサブクラスからアクセスできないことを示している。この問題を解決するためには、サブクラスのfunctionとスーパークラスのfunctionの間にリンクを作成しなければならない。もっとも早いやりかたは、サブクラスの__proto__プロパティをスーパークラスに設定することだ。サブクラスとスーパークラス の両方ともfunctionのインスタンスなので、functionのクラスメソッド(call, apply, 等々)はサブクラスからもアクセスできる。

ここで我々に必要なのは、継承チェーンを設定するためにカスタムメソッドを構築して2つのクラスをリンクさせることである。各クラスに手動で継承チェーンを設定する代わりにカスタムメソッドを使って継承チェーンを設定すると、もう一つメリットがある。実は、こうすることで、どの継承用メソッドを実装したいか選ぶこともできかつ必要ならば非常に簡単に変更することができる。これについては詳細はDave Yangによるこのポストを参照。

以下がカスタム継承関数である。これを使う必要がある:

コード:
1
2
3
4
5
Function.prototype.cExtends = function(SuperClass) {
  this.prototype = new SuperClass();

  this.__proto__ = SuperClass;
}

あるいは推奨される継承スタイルが気に入らなければ、継承については反乱勢力による方法を使うこともできる。(Dave Yangによるこの記事をチェック。私のお気に入りだ)。これは、カスタム継承関数の使用に関するよいもので、実装を非常に簡単に変更することができる。以下が反乱勢力による方法である:

コード:
1
2
3
4
5
6
Function.prototype.cExtends = function(SuperClass) {
  this.prototype.__proto__ = SuperClass.prototype;
  this.prototype.__constructor__ = SuperClass;
	
  this.__proto__ = SuperClass;
}

これだけだ。もし上記のサンプルで""Test.prototype = new SuperTest();" を "Test.cExtends(SuperTest);"に置き換えてもちゃんと動くことがわかるだろう。"cExtends"というfunction名にしたのは、"extends"がECMAScriptの予約語だからであり、"cExtendsは"custom-Extends"の略である。

OK。うまく動いているようだが、大規模にテストしているわけではないのでインスタンスによっては問題があるかもしれない。これについてなにか問題に遭遇したら知らせてほしい。

傍注: 「反乱勢力」というのはFlashCoders ML(Dave Yangが持ち出したのだと思うが確かではない)で何度か現われた非公式な名前である。これは、 "new SuperClass();"という伝統的な方法の代わりに"__proto__"を使って継承チェーンを設定することを好む人々について言っているのだ。Flash 5では伝統的な継承を行うとバグが出やすく、サブクラスprototypeチェーン内にスーパークラスのダミーインスタンスを作成することを皆嫌ったことから始まった。Flash MXによって反乱勢力は死に絶えると考えられていた。なぜならMXの新機能 "super" の侵害になるし、伝統的な方法におけるバグは修正されたからだ。ところがダミーインスタンスの問題はまだ存在し、ありがたいことにPeter Edwardsが反乱勢力のためにUndocumentedな"__constructor__"参照による解決法を発見してくれた。 彼のポストを参照。反乱勢力よ永遠なれ!

 

staticなメソッド?

staticプロパティと同じように、staticメソッドはクラスのインスタンスを生成せずに直接クラスから呼び出すことができるメソッドである。Javaではstaticメソッドはクラスのインスタンスから呼び出すこともできる。

staticメソッドは "this"参照を使ってインスタンスプロパティにアクセスすることはできない。Javaのstaticメソッドの中に"this"を置くと、次のようなコンパイルエラーが出るだろう: "non-static variable this cannot be referenced from a static context"(staticでない変数thisはstaticコンテキストからは参照できません)

しかしstaticメソッドはクラスのstaticプロパティにアクセスできる(筋が通ってるでしょ?)。クラスとクラスのprototypeにfunctionを直接割り当てることができるが、呼び出しのスコープは異なっており"this"参照は各function毎に変更される。

上記のaddStaticPropertyというfunctionを使ってfunctionをstaticプロパティに割り当てることもできる。これはメソッドとして振る舞うが、こうしたところで、staticメソッドからはどのstaticプロパティにもアクセスできない。これではせっかくのメソッドが役に立たなくなる: staticプロパティもインスタンスプロパティもどちらもアクセスできず、完全にスタンドアロンのメソッドである必要がある。

新たな制約を追加することでstaticメソッドを作成することが可能になる。基本的に、staticメソッドに必要なことはクラス自体への参照として"this"参照を使うことだ。この参照から、staticメソッドはstaticプロパティにアクセスすることができるようになる。

以下のコードでstaticメソッドを追加する方法を示す:

コード:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.addStaticMethod = function(name, theFunc) {
  var _theClass = this;

  var getter = function() {
    return theFunc;
  };
  var setter = function(meth) {
    theFunc = function() {
      return meth.apply(_theClass, arguments);
    };
  };
  setter(theFunc);

  this.addProperty(name, getter, setter);
  this.prototype.addProperty(name, getter, setter);
}
ASSetPropFlags(Function.prototype, "addStaticMethod", 1);

ご覧の通り、staticメソッドを扱うのはちょっとむずかしい。ここでは、functionのネストが3段になっている。にも関わらず、ちゃんと動いている。ここにテスト用スクリプトを挙げる。(addStaticPropertyaddStaticMethodの両方ともムービー内に存在するものとする):

コード:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
41
42
43
Test = function() {};
Test.addStaticProperty("prop1", 5);

t = new Test();
t.prop2 = 15;

trace(t.prop1); // 5
trace(t.prop2); // 15

theMeth = function(arg1, arg2, arg3) {
  trace("arg1: [" + arg1 + "] - " + 
      "arg2: [" + arg2 + "] - " + 
      "arg3: [" + arg3 + "] - " + 
      "this.prop1: [" + this.prop1 + "] - " + 
      "this.prop2: [" + this.prop2 + "]");
}

// staticメソッドを追加する
Test.addStaticMethod("meth", theMeth);

trace("===");

// パラメータを与えてテストする
t.meth("hello", "there", "tim");
Test.meth();

trace("===");

t.prop1 = 6;

t.meth();
Test.meth();

trace("===");

// staticメソッドを上書きする
Test.meth = function(arg) {
  trace(arg + " - " + this.prop1);
}

t.meth("hello");
Test.meth();
出力:

5
15
===
arg1: [hello] - arg2: [there] - arg3: [tim] - this.prop1: [5] - this.prop2: []
arg1: [] - arg2: [] - arg3: [] - this.prop1: [5] - this.prop2: []
===
arg1: [] - arg2: [] - arg3: [] - this.prop1: [6] - this.prop2: []
arg1: [] - arg2: [] - arg3: [] - this.prop1: [6] - this.prop2: []
===
hello - 6
- 6

このコードは、パラメータが適切に渡されstaticプロパティが"this"参照を使ってstaticメソッドから取得され得ることを示している。覚えておこう: staticメソッド内の"this"はクラスのインスタンスを表しているのでは「ない」。これはクラス自体を表している。37行目は、staticメソッドも他の通常のメソッドと同じように上書きすることができることを示している。

とはいっても、staticメソッドはstaticプロパティと同じ問題を抱えている: 継承では、staticメソッドはサブクラスを取得できないので、継承を設定するためにはまた"cExtends"メソッドを使わなければならない。

正直なところ、私はActionScriptではJavaスタイルのstaticメソッドの必要を感じないが、実装することは可能に思えるので、ここでそれを示そうと考えた。何かよい使用法をみつけたら教えてほしい。

一方で、staticプロパティはとても便利なものだ。

 

privateプロパティ

Javaでは、privateプロパティはインスタンスによって操作されるが他の外部スクリプトからは操作できないプロパティである。Flash MXのActionScriptでは本当のprivateプロパティを扱うことができない。しかし、スコープチェーンとネストしたfunctionをつかうことで、外側のfunctionのアクティベーションオブジェクトに属するプロパティを生成するとこれはどのスクリプトからも隠蔽される。このプロパティにアクセスする唯一の方法は、後で内側のfunction において使うことだけだ。このプロパティを実際にprivateプロパティと呼ぶことはできないが、privateプロパティに似た保護のメカニズムを提供することになる。

前に指摘したように、一度値がfunctionのスコープチェーンに確保されると、その値はfunctionを保持するオブジェクトを含む全てのものから隠蔽される。このせいであまり便利とは言えない。特に、別のfunctionから内部の値にアクセスする必要があり、オブジェクト自体からの(明らかに外部のスクリプトからではない)getter/setterの使用を迂回する必要があるとしたらどうだろう?

このような便利なprivateプロパティを生成するための考えられる最良の方法は、全てのprivateプロパティを、クラス自体のアクティベーションオブジェクトを使って生成することだ。こうすることで、全てのprivateプロパティが同じアクティベーションオブジェクトの中に存在することになる。そのために、各privateプロパティのgetter/setterが他の全てのprivateプロパティに(明示的なスコープを使わずに)アクセスすることができる。その上、getter/setterは"this"参照を使ってpublicプロパティを取得することもできる。

欠点としては、まだpublicメソッドはprivateプロパティにアクセスできずgetter/setterを使わなければならないということがある。また、クラスの各インスタンスはそれぞれのprivateプロパティ用に独自にgetter/setterを持つことになる。privateプロパティを実装しているクラスからたくさんのインスタンスが生成されたら、致命的なオーバーヘッドになる。

とにかく、サンプルコードを見てみよう:

コード:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
theClass = function(name, age) {
  // ageはpublicプロパティ
  this.age = age;

  // nameはprivateプロパティ
  this.getName = function() {
    return name;
  }
  this.setName = function(val) {
    name = val;
  }

  // もう一つprivateプロパティを追加できる
  var hairColor;
  this.getHairColor = function() {
    trace(name +
      " has " + hairColor + " hair and is " + this.age + " years old.");
    return hairColor;
  }
  this.setHairColor = function(val) {
    hairColor = val;
  }
}

p = new theClass("Tim", 25);
p.setHairColor("brown");
h = p.getHairColor();
出力:

Tim has brown hair and is 25 years old.

上記のコードでは、どうやって全てのgetter/setterが全てのprivateプロパティとpublicプロパティにアクセスするのか、"getHairColor""メソッドがどうやってprivateプロパティである"hairColor"と"name"両方とpublicプロパティである"age"にアクセスするのかがわかるようになっている。

このようにすることで、getterとsetterをインスタンス毎にいちいち作る必要がなくなり、アクティベーションオブジェクトの中にたくさんのprivateプロパティを実際に生成することができる。実は、getterとsetterに縛られる必要はない。privateプロパティに直接アクセスする必要があるメソッドはどれもconstructorに入れておくことができる。ただ、constructorに配置するメソッドが多くなればなるほど多くのメモリを消費するということは心に留めておこう。各インスタンスが自分専用バージョンのメソッドを持っていることになるからだ。

もしprivateプロパティをお互いにアクセス可能な状態にしておくことをあまり気にしないというのなら、あるいはどのオブジェクトに対してもprivateプロパティを追加したいというのであれば、そのクラスがなんであれ、addProperty2(上記参照)を使って仮想プロパティを追加すればよい。仮想プロパティは欲しくないがgetter/setterの使用を強制したいのなら、以下のコードを使ってprivateプロパティとそれに関連するgetter/setterをオブジェクトに追加すればよい(addProperty2で紹介された2つの追加の制約はまだ適用される):

コード:
1
2
3
4
5
6
7
8
9
10
11
Object.prototype.addPrivateProperty = function(name, getter, setter) {
  var prop;
  this["get" + name] = function() {
      return getter.call(this, prop);
  };

  this["set" + name] = function(val) {
      prop = setter.call(this, val);
  };
}
ASSetPropFlags(Object.prototype, "addPrivateProperty", 1);

以下のコードをみてどのように動作するか見てみよう(特にお年を召した女性向け):

コード:
1
2
3
4
5
6
7
8
9
10
11
getter = function(internalVal) {
  return internalVal / 2;
}
setter = function(val) {
  return val;
}

aWoman = new Object();
aWoman.addPrivateProperty("age", getter, setter);
aWoman.setAge(42);
trace(aWoman.getAge()); // 21
出力:

21

このaddPrivateProperty というfunctionを使えば、同じオブジェクトに2つのprivateプロパティを作ったとしても各privateプロパティは自分自身のアクティベーションオブジェクトに入っており他のプロパティのgetter/setterからアクセスされることはない。自分自身のgetter/setterだけが使われる。これは有用かもしれないしそうでないかもしれないが、またなにかこれのうまい使い方を見つけてもらえればと思う。

私自身、もし実際にprivateプロパティが必要なら、おそらくconstructorのアクティベーションオブジェクトを使っての実装にこだわるだろう。

 

結び

OK。これで終りだ。この記事では、スコープチェーンとネストしたfunctionの使い方、とりわけパラメータ化されたfunctionの作り方、クラスにstaticプロパティとメソッドを追加する方法、疑似privateプロパティを生成する方法を見てきた。

正直に言えば、私はここで論じられていることのどれかでも実際に使った実地のアプリケーションは一つも作っていない :p しかし、少なくともstaticプロパティの実装については可能性があると言っておきたい。それに、このトピックについて調べるのは楽しかったので、自分のコードを共有できるようにしようと考えた。 cExtends, addStaticProperty, addStaticMethod, addProperty2, addPrivatePropertyメソッドを含んだActionScriptライブラリのファイルは以下でダウンロード可能である。

もしaddProperty2をコンストラクタ内で、あるいはaddPrivatePropertyを使ってprivateプロパティの機能を利用するならメモリ使用量というオーバーヘッドを心に留めておこう。例えば、コンストラクタが各インスタンス毎に10個privateプロパティを生成して10個のgetter/setter(20個のfunction)を持っているのは、もし100個インスタンスが必要だとしたらあまり効率的とはいえない。こういう場合は明らかに、自分のアプリケーションにおいてprivateプロパティが本当に不可欠なのかどうか考えなければならない。

何か有用なものを構築するために上記の色々なサンプルコードを敢えて利用しようと思うなら、一言知らせてくれるとうれしい。また、このページのコード、英語、その他に間違いを見つけたら、あるいはなにかアイデアを共有したいなら、気軽にメールしてほしい

ここにファイルがある:

static_private.as

 


元記事のURLはhttp://www.timotheegroleau.com/Flash/articles/private_static.htmです。
日本語訳は、原著者であるTimothee Groleau氏の許可を得てFACEs Projectが行いました。