とりあえず雑記帳(跡地)
JavaScriptでクラスもどき
最終更新:
fujiyan
-
view
WebコミックLibraryhttp://web-comi.appspot.com/ GAE/JとSlim3で作成してみた、各出版社から配信されているWebコミックをまとめて閲覧できるサイトです。只今、実験運用中… |
Functionオブジェクトあれこれの内容を総動員して、クラスっぽいことが実現できます。
クラスもどき
- 下記のようなJavaベースの擬似コードで表現されたクラスを仮定します。
class Foo {
var propertyA;
Foo(str) {
this.propertyA = str;
}
void methodA(mark) {
alert(mark + this.propertyA + mark);
}
}
- クラス名はFooです。
- プロパティpropertyAを持ちます。
- コンストラクタを持ち、引数として文字列を受け取り、propertyAに設定します。
- methodAを持ち、引数として文字列を受け取り、propertyAの内容をその文字列で囲んで表示します。
- 上記のクラスを、JavaScriptでそれっぽく実現すると下記のようなコードになります。
var Foo = function(str) {
this.propertyA = str;
}
Foo.prototype.methodA = function(mark) {
alert(mark + this.propertyA + mark);
}
// 以下、使用例
var obj1 = new Foo("obj1");
obj1.methodA("***");
var obj2 = new Foo("obj2");
obj2.propertyA = obj2.propertyA + "@@@";
obj2.methodA("+++");
小分けにして説明
var Foo = function(str) {
this.propertyA = str;
}
- クラス名を名前にした変数Fooを定義し、コンストラクタとなるFunctionオブジェクトを設定します。
- これにより、new Foo()という記述で、オブジェクトの生成が可能になります。
- コンストラクタの内部で、プロパティとなるpropertyAを、new演算子で生成した際のコンテキストオブジェクトに定義します。
- これにより、new Foo()で生成したオブジェクトには、全てpropertyAが定義されることになります。
- コンストラクタは、引数strを受け取り、生成したオブジェクトのpropertyAに設定します。
Foo.prototype.methodA = function(mark) {
alert(mark + this.propertyA + mark);
}
- コンストラクタとなるFunctionオブジェクトFooのprototypeに対して、メソッド名を名前にしたプロパティmethodAを定義し、メソッドとなるFunctionオブジェクトを設定します。
- これにより、new Foo()で生成したオブジェクトに対して、obj1.methodA()という表記で、メソッドとなるFunctionオブジェクトが実行できます。
- obj1.methodA()と呼び出した際は、methodA内のthisは、obj1を指します。
クラス継承もどき
- さらに、継承っぽいこともやらせてみましょう
- やっぱり、下記のようなJavaベースの擬似コードで表現されたクラスを仮定します。
// クラスFooの定義は、上記と同じ
class ExtendedFoo extends Foo {
var propertyB;
ExtendedFoo(strA, strB) {
super(strA)
this.propertyB = strB;
}
void methodA(mark) {
alert(mark + this.propertyA + this.propertyB + mark);
}
void methodB(mark) {
alert(mark + this.propertyB + mark);
}
}
- クラス名はExtendedFooです。
- クラスFooを継承しています。
- Fooで定義されたプロパティの他、独自のプロパティpropertyBを持ちます。
- コンストラクタを持ち、引数として文字列を2つ受け取ります。
- 最初にスーパークラスFooのコンストラクタを呼び出します。引数にstrAを指定します。
- strBは、propertyBに設定します。
- Fooで定義されたmethodAをオーバーライドします。引数として文字列を受け取り、propertyAとpropertyBの内容を連結し、その文字列で囲んで表示します。
- 独自のメソッドmethodBを持ちます。
- 上記のクラスを、やっぱりJavaScriptでそれっぽく実現すると下記のようなコードになります。
var ExtendedFoo = function(strA, strB) {
Foo.apply(this, [strA]);
this.propertyB = strB;
}
ExtendedFoo.prototype = new Foo();
ExtendedFoo.prototype.methodA = function(mark) {
alert(mark + this.propertyA + this.propertyB + mark);
}
ExtendedFoo.prototype.methodB = function(mark) {
alert(mark + this.propertyB + mark);
}
//以下、使用例
var obj1 = new ExtendedFoo("obj1", "abc");
obj1.methodA("***");
obj1.methodB("***");
var obj2 = new ExtendedFoo("obj2", "xyz");
obj2.propertyA = obj2.propertyA + "@@@";
obj2.methodA("+++");
obj2.methodB("+++");
やっぱり小分けにして説明
var ExtendedFoo = function(strA, strB) {
Foo.apply(this, [strA]);
this.propertyB = strB;
}
- クラス名を名前にした変数ExtendedFooを定義し、コンストラクタとなるFunctionオブジェクトを設定します。
- これにより、new ExtendedFoo()という記述で、オブジェクトの生成が可能になります。
- コンストラクタの内部で、FunctionオブジェクトFooのapply()メソッドを呼んでいます。
- apply()メソッドを適用するコンテキストオブジェクトとして、this(new演算子で生成した際のコンテキストオブジェクト)を指定しています。
- apply()メソッドの第2引数には、Foo本来の引数を配列で指定しています。値として、strAを渡しています。
- これにより、new ExtendedFoo()で生成したオブジェクトに対して、Fooの初期処理が適用されることになります(プロパティ継承とsuper()のエミュレート)。
- apply()メソッドを適用するコンテキストオブジェクトとして、this(new演算子で生成した際のコンテキストオブジェクト)を指定しています。
- その後、ExtendedFooの独自プロパティとなるpropertyBを、new演算子で生成した際のコンテキストオブジェクトに定義します。
- これにより、new ExtendedFoo()で生成したオブジェクトには、全てpropertyBが定義されることになります。
- コンストラクタは、引数strBを生成したオブジェクトのpropertyAに設定します。
ExtendedFoo.prototype = new Foo();
- ExtendedFooのプロパティprototypeに対して、Fooをコンストラクタとしたオブジェクトを設定しています。
- これにより、Fooのメソッドとなるプロパティを、ExtendedFooをコンストラクタとしたオブジェクトからも参照できるようになります(メソッド継承のエミュレート)。
ここは詳しく説明
- クラス継承もどきを実現するには、new ExtendedFoo()で作成したオブジェクトのプロトタイプチェーンに、
- ExtendedFooのメソッドに当たるFunctionオブジェクト
- Fooのメソッドに当たるFunctionオブジェクト
- を組み込むようにします。
- ExtendedFooのメソッドについては、ExtendedFoo.prototypeに、メソッドに当たるFunctionオブジェクトのプロパティを定義します。
- これによって、作成されたオブジェクトの__proto__にExtendedFoo.prototypeが設定されるので、プロトタイプチェーンに組み込まれます。
- では、Fooのメソッドに当たるFunctionオブジェクトをプロトタイプチェーンに組み込むには?
- ExtendedFooの親クラスがFooという継承階層に倣って、__proto__にはExtendedFooのメソッドを、プロトタイプチェーンの親に当たる__proto__.__proto__に、Fooのメソッドが設定されるようにすれば目的は達成されます。
- new Foo()で作成したオブジェクトの__proto__にFoo.prototypeが設定されるので、そのオブジェクトがnew ExtendedFoo()で作成したオブジェクトの__proto__に設定されればいいですね(Foo.prototypeにFooのメソッドが定義されているので)。
- ということで、ExtendedFoo.prototypeにnew Foo()で作成したオブジェクトを設定しているのです。
- ExtendedFooのメソッドは、new Foo()で作成したオブジェクトのプロパティとして設定していきます。
- なお、Fooのメソッドを呼ぶ際には、new Foo()で作成したオブジェクトに設定されたFunctionオブジェクトが用いられますが、コンテキストオブジェクトはnew Foo()で作成したオブジェクトではなく、メソッド起動時に指定したオブジェクト(new ExtendedFoo()で作成したオブジェクト)です。
- あと、new Foo()でコンストラクタ引数を渡していませんが、これは渡す必要が無いからです。new Foo()で生成したオブジェクトで利用したいのはFunctionオブジェクトだけであり、それ以外のプロパティは利用しないためです。
ExtendedFoo.prototype.methodA = function(mark) {
alert(mark + this.propertyA + this.propertyB + mark);
}
ExtendedFoo.prototype.methodB = function(mark) {
alert(mark + this.propertyB + mark);
}
- コンストラクタとなるFunctionオブジェクトExtendedFooのprototype(new Foo()で生成したオブジェクト)に対して、メソッド名を名前にしたプロパティmethodAを定義し、メソッドとなるFunctionオブジェクトを設定します。
- これにより、new Foo()で生成したオブジェクトに対して、obj1.methodA()という表記で、メソッドとなるFunctionオブジェクトが実行できます。
- methodAは、Foo.prototypeでも定義されていますが、プロトタイプチェーンの検索順序により、ExtendedFoo.prototypeで定義したほうが実行対象となります(メソッドのオーバーライドのエミュレート)。
- obj1.methodA()と呼び出した際は、methodA内のthisは、obj1を指します。
- また、ExtendedFooの独自メソッドとして、メソッド名を名前にしたプロパティmethodBを定義し、メソッドとなるFunctionオブジェクトを設定します。
- これにより、new Foo()で生成したオブジェクトに対して、obj1.methodB()という表記で、メソッドとなるFunctionオブジェクトが実行できます。
- obj1.methodB()と呼び出した際は、methodA内のthisは、obj1を指します。