SSブログ

JavaScript のクラスの作成 - 2 ~プロトタイプ編~ [JavaScript]

◆JavaScript のクラスの作成 - 2 ~プロトタイプ編~

前回のエントリー「JavaScript のクラスの作成 - 1 ~基本編~」の続きです。

今回は、JavaScript の「クラス」の作成の中でも、「プロトタイプ」に注目します。


まずは、前回のエントリーの「クラス定義」を、改めて提示しておきます。

function ClassA() {
    this.alertWord = function (word) { alert(word); }
    this.alert1 = function () { this.alertWord('ClassA 1'); }
    this.alert3 = function () { this.alertWord('ClassA 3'); }
    return this;
}
ClassA.prototype.constructor = ClassA;
ClassA.prototype.alert2 = function () { this.alertWord('ClassA 2'); }

今回のエントリーでも、これをいろいろと「こねくりまわし」ます。


前回delete キーワードを使って、インスタンス プロパティを削除しました。

これとまったく同じことが、プロトタイプ プロパティに対しても可能です。

// alert2 を、インスタンスとプロトタイプで定義する
function ClassA_DoubleAlert2() {
    this.alertWord = function(word) { alert(word); }
    this.alert1 = function() { this.alertWord('ClassA_DoubleAlert2 1'); }
    this.alert3 = function() { this.alertWord('ClassA_DoubleAlert2 3'); }
}
ClassA_DoubleAlert2.prototype.constructor = ClassA_DoubleAlert2;
ClassA_DoubleAlert2.prototype.alert2 = function() { this.alertWord('ClassA_DoubleAlert2 2 Prototype'); }

// インスタンス作成
var ins = new ClassA_DoubleAlert2();

// インスタンスの alert2 実行
ins.alert2(); // -> 'ClassA_DoubleAlert2 2 Constructor'(プロトタイプ)

// プロトタイプ プロパティを削除
delete ClassA_DoubleAlert2.prototype.alert2;

// 改めて alert2 を見る
alert(ins.alert2); // -> undefined(名前を解決できない)

いちおうノウハウとして書いておきますね。


先程の ClassA のクラス定義では、無名関数を使って、控えめに alert2 メソッドを定義してました。

前回のエントリーで示した通り、"this" キーワードを使って(コンストラクタ内で)定義している alertWord・alert1・alert3 メンバはインスタンスごとの初期化として、prototype を使って定義している alert2 メンバはクラスごとの初期化として向いています。

むしろ、クラスごとに役割を明確にしたい場合は、"this" スコープでの属性定義を使わずに、プロトタイプの定義だけを使ったほうが、(理解しやすいというのもあるし)利便性が高いかもしれません。

こんなふうに。

// プロトタイプだけでメンバを定義するクラス定義
function ClassA_Prototype() { return this; }
ClassA_Prototype.prototype.constructor = ClassA_Prototype;
ClassA_Prototype.prototype.alertWord = function(word) { alert(word); }
ClassA_Prototype.prototype.alert1 = function() { this.alertWord('ClassA 1'); }
ClassA_Prototype.prototype.alert2 = function() { this.alertWord('ClassA 2'); }
ClassA_Prototype.prototype.alert3 = function() { this.alertWord('ClassA 3'); }

単純な「クラス定義」という視点で見ると、先に示した ClassA クラスと、ここで定義した ClassA_Prototype クラスは、「そのクラス単独では」同じ働きをします。

(「そのクラス単独では」っていうところに、ちょっと「ミソ」が隠れてるんですけどね)


一方、JavaScript の定義では、スコープの違う定義を使って、ひとつの属性定義を再利用して、異なるスコープでおおっぴらに異なる動作をする属性を定義することもできます(prototype に限らず)。
// クラスとコンストラクタ定義
function ClassA_PrototypeNamedFunction() {}
ClassA_PrototypeNamedFunction.prototype.constructor = ClassA_PrototypeNamedFunction;

// window スコープの alertWord 関数
function alertWord(word) { alert(word); }

// window スコープの alertWordMember 関数
function alertWordMember(word) { alert('Member: ' + word); }

// ClassA_PrototypeNamedFunction スコープで alertWord を解決するときは、
// window.alertWordMember 関数を使う

ClassA_PrototypeNamedFunction.prototype.alertWord = alertWordMember;
// ちょっといじわるw

// window スコープで関数を定義し、それを
// ClassA_PrototypeNamedFunction スコープのプロトタイプに設定していく

function alert1() { this.alertWord('alert1'); }
ClassA_PrototypeNamedFunction.prototype.alert1 = alert1;
//
function alert2() { this.alertWord('alert2'); }
ClassA_PrototypeNamedFunction.prototype.alert2 = alert2;
//
function alert3() { this.alertWord('alert3'); }
ClassA_PrototypeNamedFunction.prototype.alert3 = alert3;

// window スコープで各関数を実行
alert('上位スコープ関数を実行');
alertWord('アラートメッセージ'); // -> 'アラートメッセージ'
alert1();//  -> 'alert1'
alert2();//  -> 'alert2'
alert3();//  -> 'alert3'

// ClassA_PrototypeNamedFunction クラス インスタンスを確保して、
// そのスコープで各関数を実行

alert('ClassA_PrototypeNamedFunction クラス インスタンスを実行');
var ins = new ClassA_PrototypeNamedFunction();
ins.alertWord('アラートメッセージ'); // -> 'Member: アラートメッセージ'
ins.alert1();// -> 'Member: alert1'
ins.alert2();// -> 'Member: alert2'
ins.alert3();// -> 'Member: alert3'

 う~ん・・・・・・このブログで JavaScript を勉強しようとしている人には、恨まれそうなエントリーだなあ;;;

先に提示した ClassA クラスや、ClassA_Prototype クラスと、ここで定義した ClassA_PrototypeNamedFunction クラスは、単独のクラスとしてみれば、動作はあまり変わりません。

ただ、このソースを組み込むと、どのスコープで alertWord を使うかによって、alertWord の動作が変化します。

ここで定義した ClassA_PrototypeNamedFunction クラスのスコープで alertWord を解決しようとすると、実際には window.alertWordMember() 関数 が呼ばれ、alert で表示される文字列のプレフィックスとして、必ず 'Member: ' が付加されます。

一方、Window(または DOMWindow)インスタンス スコープで alertWord を使うと、そのまま window.alertWord() 関数 が呼ばれ、alert で表示される文字列のプレフィックスとして 'Member: ' が付加されることはありません。

"this" の内容を、トリッキーに使ってるんですね。

まず、 alertWord() 関数、alertWordMember() 関数、alert1() 関数、alert2() 関数、alert3() 関数の5つは、ClassA_PrototypeNamedFunction クラス メンバとして作成したわけではありません。

Window(または DOMWindow)インスタンス スコープで定義しています。

だから、Window(または DOMWindow)インスタンス スコープで呼んでも、動きます。

例えば、上記のソースの中にあるように、いきなり alert1(); としても、動作します。

とりわけ、alert1() 関数、alert2() 関数、alert3() 関数の3つは、それらの関数が呼ばれたスコープの "this" にある "alertWord" を呼び出すように作ってあるだけです。

だから、alert1() 関数、alert2() 関数、alert3() 関数は、どのスコープで実行されていても、そのスコープの "alertWord" という名前を解決して、その名前で定義されているメソッドを呼び出そうとします。

で、問題の alertWord は、"this" のスコープによって、実行される内容が変化します。
  1. ClassA_PrototypeNamedFunction スコープで呼ばれた場合は、window.alertWordMember() 関数を実行。
  2. Window または DOMWindow インスタンス スコープで呼ばれた場合は、window.alertWord() 関数を実行。
この状況の指定は、先のソースの中の、以下の部分で定義しています。
// window スコープの alertWordMember 関数
function alertWordMember(word) { alert('Member: ' + word); }

// ClassA_PrototypeNamedFunction スコープで alertWord を解決するときは、
// window.alertWordMember 関数を使う

ClassA_PrototypeNamedFunction.prototype.alertWord = alertWordMember;
// ちょっといじわるw

これは、Window または DOMWindow インスタンスの中で alertWord() 関数を一度定義し、alertWordMemer() 関数も一度しているけれど、ClassA_PrototypeNamedFunction クラス スコープの alertWord 名前解決については、window.alertWordMemer() 関数を呼びなさい、という prototype 定義です。 

念のために、もう一度書いておきますが、これは「プロトタイプだから」ってわけじゃないですからね。

JavaScript は、こういうものなんです。

・・・・・・・めちゃめちゃ「ややこしい」よなあ、きっと;;;。

ついて来れないやつ、おいてくぞーーー!!!


なお、クラスでは、<クラス名>.prototype の他に、<クラス名>.__proto__ という属性も持っていることがあります。

「持っていることがあります」というのは、__proto__ は、ブラウザ依存なんです。

Internet Explorer では、この __proto__ を持っていません。

FirefoxOperaSafariGoogle Chrome では __proto__ を持っていますが、その中身もやっぱりブラウザ依存です。

前述の ClassA について、各々のブラウザで __proto__ の中身を見てみると、以下のようになっています。

◆ClassA のブラウザごとの __proto__ の中身
Internet Explorer 8undefined
Firefox 3.6.13function() {}
Opera 11.00
function () { [native code] }
Safari 5.0.3function () { [native code] }
Google Chrome 8.0.552.215function Empty() {}

この __proto__、深堀りしていくと、実におもしろそうです。

しかし、個人的に JavaScript では、「ブラウザ間の差異を吸収しながら、どうやっていろいろな環境で同じ動作をさせるか」という点に力点を置いているので、ひとつでも対応していないブラウザがある場合は、ちょっと手を出しづらいです。

ということで、このエントリーでは「__proto__ ってのがあるんだよ」というところまでにとどめておきます。


前回のエントリーで示した通り、このような定義を prototype で行えば、その動作をクラスごとに定義できます。

クラスのインスタンス(実体)をいくつ作っても、それらのインスタンスで共通の動作が保障されます。

しかも、その動作は、動的に変化させることができます。

一方、このような定義を、そのスコープのインスタンスの属性(例えば、コンストラクタで "this" を使った属性)に対して行うと、JavaScript では prototype よりもインスタンスが持っている属性定義が先に解決されるため、インスタンスごとの定義が優先されます。

クラス インスタンスを作ってから、そのクラスで prototype 定義されている名前を同名で「後付け」定義すると、「同名定義を許容する」/「より後で定義された名前の定義を有効とする」という JavaScript の特性によって、インスタンスごとに振る舞いを変えることができます。

いや~、おもしろいね。

・・・・・・ところが。

クラスの作り方によっては、prototype が呼べないケースがあります。

実際には prototype が設定されているけれど、インスタンスが prototype の名前を解決できない、というケースです。

・・・・・・と、いうか。

わざとそういう作りにするんです。

いや~、奥深いね!

・・・・・・ということで、次回は「クラスの作成 - 3 ~クロージャ編~」いきましょう。
nice!(2)  コメント(5)  トラックバック(0) 
共通テーマ:パソコン・インターネット

nice! 2

コメント 5

One-for-you

二次元目は、ダイヘン頼んだのに、来ちゃった。

ついていけないので、おいていって下さい、出席番号一番、そして落ちこぼれも一番ww  年末・年始に出直してきますMaybe \end
by One-for-you (2010-12-21 22:43) 

ゆうみ

代返頼んでても来てるじゃん。
ついていけないのは 私だよ。
でも 懸命に眺めてるの。
わっかんないんだけどさ
by ゆうみ (2010-12-22 12:12) 

みみちゃん

すみません、これ全部下書きでため込んだのを、1日ずつ公開してるだけです;;;。
おっかしいなあ、まだ仕事の役に立つようなエントリーが出てこないぞ;;;。
本来の趣旨から外れとるがな;;;。
by みみちゃん (2010-12-22 20:27) 

One-for-you

うん、でもね、下書きでも、この生徒は、未だWordーBankでさえついていけないんだけど、どっかで、一皮・脱皮させて頂くトリガになると思っております。

目標は10年後なんですけど、5年後に短縮しようかってモチベも上がってきてますよwww
by One-for-you (2010-12-28 01:14) 

みみちゃん

この blog、徐々に網羅するエリアを広げて行くかもしれないです。
プログラムは、毎日触っていないと、どんどん忘れていきます。
1年後・あるいは5年後に、もしかしたら JavaScript から離れてしまっているかもしれない自分のために。
そのうち、基礎も掘り下げて行こうかと・・・・・・。
by みみちゃん (2010-12-28 15:52) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。

この広告は180日新規投稿のないブログに表示されます