JavaScriptのオブジェクト指向プログラミング
前回のおさらい
var 変数名 = new コンストラクタ名(引数,引数,…) //オブジェクトリテラルを利用してオブジェクトを生成 var 変数名 = { プロパティ名 : 値, プロパティ名 : 値,…};
オブジェクトのプロパティやメソッドの利用
変数名.プロパティ名 変数名.メソッド名(引数,引数,…)
プロパティ
プロパティへの代入とプロパティ定義
Javascriptでは事前にプロパティを宣言する必要がない。
オブジェクトに定義されていないプロパティに値を代入すると自動的にプロパティが定義されて値が代入される。
すでに定義されているプロパティに値を代入するとプロパティ値が変更される。
var obj={}; document.write('プロパティ代入前:'+obj.prop+'<br>'); obj.prop=1; document.write('プロパティ代入後:'+obj.prop+'<br>');
プロパティの削除
プロパティを削除するにはdelete文を使用する。
delete オブジェクト名.プロパティ名 delete obj.prop;
連想配列
連想配列はハッシュとも呼ばれ、数値以外のデーター型をインデックスに指定できる配列のこと。
JavaScriptのオブジェクトは連想配列の性質を持っているため、プロパティ名(文字列)を配列のインデックスに指定してプロパティを参照できる。
オブジェクト名[プロパティ名] var obj={prop1 : 1,prop2 : 'サンプル'}; //プロパティを持ったオブジェクトを定義 var value=obj['prop1']; //オブジェクトのプロパティを連想配列として参照
with文
関数内と関数外で同じ名前の変数を定義した場合、関数内では関数内で定義した変数が優先的に参照される。
with文はその優先順位を変更するための構文。
var a=1; function func(){ var a='inner'; alert('withなし:'+a); with (window) { //with文でwindowオブジェクトを指定、関数の外で定義した変数を優先する alert('withあり:'+a); } } func();
オブジェクトの記述を省略したい場合も利用できる
var obj={prop1 : '1',prop2 : '2',prop3 : '3'}; with (obj){ alert(prop2); }
クラス定義とコンストラクタ
JavaScriptはオブジェクト指向の言語にも関わらずクラス定義のための構文が定義されていない。
JavaScriptのクラス定義はコンストラクタ内でプロパティを定義することで行う。
コンストラクタの定義
コンストラクタはオブジェクトを生成するためのメソッドで、プロパティを定義したり、オブジェクト生成時の初期化処理が実行される。
function コンストラクタ名(引数,引数,…){ オブジェクトの初期化処理 } //メッセージ情報を管理するコンストラクタ function MessageInfo(message){ this.message=message; } //オブジェクトの生成 var messageInfo=new MessageInfo('hello'); alert(messageInfo.message);
大文字で始まる関数はコンストラクタ、小文字で始まる関数は一般の関数またはメソッド
thisキーワード
JavaScriptでオブジェクトのプロパティを定義するにはコンストラクタ内でthisキーワードにプロパティを設定する。
コンストラクタの引数
コンストラクタは関数のため引数を定義できる。しかしJavaScriptでは関数呼び出し時に引数を省略できるので、
全ての引数に値が設定されているものとしてコンストラクタを定義するとプロパティがundefinedになり思わぬエラーが発生することがある。
その場合は、プロパティを特定の値で初期化する、エラーを発生させプログラムを中断する、などの処理を行う。
//引数が指定されていない場合エラーを発生させる function MessageInfo(message){ if(message==undefined){ throw new Error('missing parameter \'message\'.'); } this.message=message; } //引数が指定されていない場合初期値を設定する function userInfo(userId){ if(userId==undefined){ userId='U0000'; } this.userId=userId; }
クラスプロパティ(静的プロパティ)
クラスプロパティとは同じクラス内で共有されるプロパティのこと。
JavaScriptではコンストラクタ(Functionオブジェクト)のプロパティとして定義することでクラスプロパティを実現出来る。
クラス名.プロパティ名 funcktion MessageInfo(message,priority){ this.message=message; this.priority=priority; } MessageInfo.PRIORITY_LOW='low'; MessageInfo.PRIORITY_HIGH='high'; var msg=new MessageInfo('hello',MessageInfo.PRIORITY_LOW); alert(msg,priority);
オブジェクトのクラスを判定する
JavaScriptでは変数のデータ型が固定されていないので、変数に格納されているオブジェクトのクラスやデータ型を判定して処理することが多い。
instanceof演算子による判定
オブジェクトが比較元のクラスの性質を持っているか判定出来る。
オブジェクトが指定したクラスのインスタンスの場合はtrueを、それ以外はfalseを返す。
左辺のオブジェクトが右辺のクラスのサブクラスでもtrueになる。
function MessageInfo(message){ this.message=message; } var msg=new MessageOnfo('hello'); document.write('msg MessageInfo:'+(msg instanceof MessageInfo));
constructorプロパティによる判定
constructorプロパティにはインスタンスを生成したコンストラクタへの参照が自動的に格納されるため
インスタンスがどのコンストラクタから生成されたのかを判定したい場合に利用出来る。
function MessageInfo(message){ this.message=message; } var msg=new MessageInfo('hello'); document.write('msg MessageInfo:'+(msg.constructor==MessageInfo);
ダックタイピングによる判定
使用したいプロパティ、メソッドが存在するかをチェックする。オブジェクトのデータ型にこだわらない。
より厳密な参照が求められる状況では、instanceof演算子を用いる。
//userIdをチェックしuserIdがあれば表示する function alertUserId(obj){ if(obj.userId){ //ダックタイピングによる比較 //if(obj instanceof UserInfo){ //instanceofによる比較 alert(obj.userId); } } function UserInfo(userId){ this.userId=userId; } var usr=new UserInfo('U0001'); alertUserId(usr); //U0001が表示される
メソッドの定義
メソッドの呼び出し
オブジェクト名.メソッド名(引数,引数,…) var newWindow=window.open('');
プロパティとしてメッソドを定義する
JavaScriptのかんすうは関数リテラルとして変数に代入出来る。
なのでオブジェクトのプロパティに関数リテラルを代入することで、オブジェクトにメソッドを定義てきる。
この方法ではインスタンスを2つ以上生成した場合それぞれにメソッドが生成されるので無駄。
function MessageInfo(message){ this.message=message; this.alertMessage=function(){ alert(this.message); }; } var msg=new MessageInfo('hello'); msg.alertMessage();
プロトタイプ(prototype)
prototypeオブジェクトはFunctionオブジェクトが持つ特殊なオブジェクトでメソッドごとに異なるprototypeオブジェクトを持っている。
またオブジェクトにもprototypeプロパティが用意されていて、このプロパティはコンストラクタのprototypeオブジェクトを参照している。
対象オブジェクトにプロパティが定義されていなくても、そのオブジェクトが参照するprototypeオブジェクトに定義されていれば、対象オブジェクトのプロパティのように参照出来る。
これをプロトタイプチェーンと呼ぶ。
JavaScriptではこれらの性質を利用して、コンストラクタのprototypeオブジェクトに関数リテラルを利用してメソッドを定義することで、すべてのインスタンスから1つのFunctionオブジェクトを参照させることが出来る。
function MessageInfo(message){ this.message=message; } MessageInfo.prototype.alertMessage=function(){ alert(this.message); }; var msg=new MessageInfo('hello'); msg.alertMessage();
クラスの継承
JavaScriptには継承のための構文はない。そのためプロトタイプなどを利用して実質的な継承関係を構築する。
プロパティの継承
オブジェクトのプロパティを定義しているのはコンストラクタなので、スーパークラスのコンストラクタをサブクラスから呼び出せばサブクラスでもスーパークラスのプロパティが定義された状態になる。
プロパティの継承はサブクラスからapply()メソッドを利用してスーパークラスのコンストラクタを呼び出すことで実現する。
//スーパークラス function MessageInfo(message){ this.message=message; } //サブクラス function ErrorMessageInfo(message,errorLevel){ this.errorLevel=errorLevel; MessageInfo.apply(this,[message]); //スーパークラスのコンストラクタを呼び出す } var eMsg=new ErrorMessageInfo('エラーです','error'); alert(eMsg.message);
メソッドの継承にはプロトタイプチェーンの仕組みを利用する。
function MessageInfo(message){ this.message=message; } MessageInfo.prototype.alertMessage=function(){ alert(this.message); }; //MessageInfoを継承したエラーメッセージを管理するクラス function ErrorMessageInfo(message,errorLevel){ this.errorLevel=errorLevel; MessageInfo.apply(this,[message]); } ErrorMessageInfo.prototype=new MessageInfo(); //プロトタイプチェーンを設定 var eMsg=new ErrorMessageInfo('エラーです','error'); eMsg.alertMessage();
コンストラクタでプロパティに初期値を与えると、サブクラスのプロパティとprototypeオブジェクト(スパークラスのインスタンス)で重複してプロパティを
保持してしまう。
このような場合はprototypeプロパティの設定後にdelete文を利用してprototypeオブジェクトの重複するプロパティを削除する。
ErrorMessageInfo.prototype=new MessageInfo(); delete ErrorMessageInfo.prototype.message; var eMsg=new ErrorMessageInfo('エラーです','error'); eMsg.alertMessage();
constructプロパティの設定
メソッドの継承を実現するためにprototypeオブジェクトを入れ替えたが、その影響でprototypeオブジェクトのプロパティであるconstructorの値も入れ替わっている。
本来ならconstructorはサブクラスのコンストラクタを参照すべきだが、実際はスーパークラスのコンストラクタを参照している。
なので正しく設定し直す必要がある。
ErrorMessageInfo.prototype=new MessageInfo() //プロトタイプチェーンを設定 ErrorMessageInfo.prototype.constructor=ErrorMessageInfo;
hasOwnProperty()メソッドでプロパティをチェックする
hasOwnProperty()メソッドは指定したプロパティがオブジェクトに定義されている場合はtrue、prototypeオブジェクトが指定されている場合はfalseを返す。
なお指定したプロパティが存在しない場合もfalseを返す。
プロパティが継承されたものかどうかを判定する場合に利用する。
var obj=new Array(); obj.prop1=123; //objに追加したプロパティprop1をチェック var result1=obj.hasOwnProperty('prop1'); document.write('prop1: '+result1+'<br>'); //Arrayに定義されているプロパティlengthをチェック var result2=obj.hasOwnProperty('length'); document.write('length: '+result2+'<br>'); //参照用のprototypeプロパティconstructorをチェック var result3=obj.hasOwnProperty('constructor'); document.write('constructor:'+result3+'<br>');
クラスプロパティ/メソッドの継承
あまり利用しない。
スコープと名前空間
スコープとグローバルオブジェクト
グローバル変数、関数はグローバルオブジェクトのプロパティやメソッド。
グローバルオブジェクトは特殊なオブジェクトなので、オブジェクトの指定を省略しプロパティ名やメソッド名だけで参照出来る。
関数スコープとグローバルスコープ
JavaScriptのスコープには関数内部のスコープとプログラム全体のスコープがある
- 関数スコープ - 関数内部で宣言された変数は関数内のみで参照可能。関数の外側では参照出来ない。
- グローバルスコープ - 関数の外側で宣言された変数はグローバルオブジェクトに定義され、どこからでも参照でできる。
なお関数内でvarキーワードを省略してプロパティ以外の変数を定義した場合は、グローバル変数になる。
スコープが決定するタイミング
静的スコープの挙動で注意が必要なのは関数リテラルの扱い。
関数リテラルは変数のため、実行時に静的スコープに格納されるのはFunctionオブジェクトではなくunfined。
そのためfunction文で定義した関数は定義より前で呼び出してもえらーにはならないが
関数リテラルで定義した関数は定義前で呼び出すとエラーになる。
グローバルスッコープの汚染と名前空間
一般的にプログラムのコードが増えるにつれ、プログラム全体の見通しが悪くなり、同じ名前のメソッドや変数を定義してしまうことがある。
これをグローバルスコープの汚染という。
このような問題を防ぐために、オブジェクト指向ではクラスに名前空間を持たせるのが一般的。
名前空間とは何かを理解するにはファイルシステムにおけるフォルダをイメージするとわかりやすい。
名前空間もフォルダと同じような階層構造になっているが、上位階層と下位階層は「.」で区切る。
webアプリケーションの名前空間には、一般的にインターネットドメイン名を逆順に並べたものを用いる。
名前空間の作り方
JavaXcriptで名前空間を実現するにはオブジェクトのプロパティ階層を利用する。
//名前空間を定義してオブジェクトを定義する var jp; if(!jp){ jp={}; //空オブジェクトを生成 } if(!jp.teachYourself){ jp.teachYourself={}; //空オブジェクトを生成 jp.teachYourself.MessageInfo=function(message){ this.message=message; } var msg=new jp.teachYourself.MessageInfo('hello'); alert(msg.message)
名前空間はJavaScriptのライブラリを作成する際にとても重要。
JavaScriptライブラリのファイル名と同じ名前の名前空間を使って関数やオブジェクトを定義することでグローバルスコープの汚染を防ぐことが出来る。