前回の記事でほんの少し書いたが、プライベートメソッド内に記述されたthisキーワードは、通常はwindowオブジェクトを参照してしまう。
< そもそもthisキーワードって? >
thisキーワードは、コンテキストオブジェクト を参照するためのキーワードである。
thisキーワードがwindowオブジェクトを参照するということは、その関数のコンテキストオブジェクトがwindowオブジェクトであるということになる。
< なぜコンテキストオブジェクトがwindowオブジェクトになってしまうのか? >
これは、プライベートメソッドの 呼び出し方 に原因がある。
プライベートメソッドは、プライベートフィールドに対して関数を設定することで定義できると、前回説明した。そうすると、プライベートメソッドの呼び出し方は
this.privateMethod1():
や、
obj.privateMethod1();
といった、インスタンス経由での呼び出し方はできないので、
privateMethod1();
という、直接的な呼び出し方になる。
この呼び出し方をすると、その関数のコンテキストオブジェクトがwindowオブジェクトになってしまうのである。 ( むしろ、コンテキストオブジェクトがwindowオブジェクトなのが普通であり、インスタンス経由で呼び出した時が特殊な動作をしている。これについてはこの記事の最後で。 )
< プライベートメソッドのコンテキストオブジェクトを、期待通りにするには? >
これには、Functionオブジェクトの applyメソッド 、または callメソッド を利用する。
applyメソッドとcallメソッドは、コンテキストオブジェクトを指定して関数を実行することができる。
function Hoge()
{
var privateMethod1 =
function()
{
return this;
};
this.privilegedMethod1=
function()
{
var obj1 = privateMethod1(); // windowオブジェクトが返される。
var obj2 = privateMethod1.apply(this); // Hogeオブジェクトが返される。
};
}
applyメソッドとcallメソッドの違いは、関数に渡す引数の指定方法のみである。
applyメソッドは、第一引数でコンテキストオブジェクトを受け取り、第二引数で関数に渡す引数の配列を受け取る。
callメソッドは、第一引数でコンテキストオブジェクトを受け取り、第二引数以降で関数に渡す引数を受け取る。
function fuga(arg1, arg2)
{
}
var context = new Object();
var arg1 = 1;
var arg2 = 2;
var args = [arg1, arg2];
fuga.apply(context, args); // applyは第二引数に配列で指定する。
fuga.call(context, arg1, arg2); // callは第二引数以降に指定していく。
< 呼び出し方が原因ということは・・・ >
実は、パブリックメソッドやプレビレッジドメソッドも、同じ呼び出し方をすれば、コンテキストオブジェクトがwindowオブジェクトになる。
function Hoge()
{
}
Hoge.prototype.publicMethod1 =
function()
{
return this;
};
var hogeObj = new Hoge();
var obj1 = hogeObj.publicMethod1()) // 当然、hogeObjを返してくる。
var func = hogeObj.publicMethod1; // publicMethod1自体をfuncに入れる。
var obj2 = func() // func()という風に呼び出すと、windowオブジェクトを返してくる。
< インスタンス経由での呼び出しは暗黙的に・・・ >
this.メソッド名() や、 インスタンス名.メソッド名() といった、インスタンス経由での呼び出し方をすると、そのインスタンスがコンテキストオブジェクトとなる。しかし、applyやcallも使わず、普通に関数を呼び出した場合、コンテキストオブジェクトはwindowオブジェクトとなる。
つまり、インスタンス経由での関数呼び出しは、暗黙的にコンテキストオブジェクトを指定して実行されるのである。 ( 内部でapplyやcallを呼び出しているのか、applyやcallに実装されている処理を直接行っているのかどうかは知らないけど。。。 )
ただし、”クラスベース”のオブジェクト指向言語 ( C#やJavaなど ) ではなく、”プロトタイプベース”のオブジェクト指向言語である。
以下、JavaScriptでのOOPについての簡単な解説。
【 コンストラクタ 】
オブジェクトの雛形は " コンストラクタ " と呼ばれる関数として定義する。これは、クラスベース オブジェクト指向言語の " クラス " に相当する。コンストラクタは関数なので、普通の関数と同じように呼び出すこともできる。 ( 普通、そんな使い方はしないけど。 )
コンストラクタからオブジェクトを生成するには、new 演算子を用いる。
// Hogeコンストラクタ
function Hoge()
{
}
// Hogeオブジェクト生成
var hogeObj = new Hoge();
[ thisキーワード ]
コンストラクタ内 ( と、後述するインスタンスメソッド内 ) では、thisキーワードによって、コンストラクタのインスタンスを参照することができる。
ただし、プライベートメソッド内では、通常は、コンストラクタのインスタンスではなくwindowオブジェクトを参照してしまうので注意。( 詳細はこちらの記事 )
【 インスタンスフィールド 】
インスタンスフィールドは2種類に大別できる。
[ パブリックフィールド ]
パブリックフィールドには、インスタンスの外部からアクセスすることができる。
定義するには、コンストラクタ内で、そのコンストラクタのインスタンスに対して動的にフィールドを追加する。
function Hoge()
{
this.publicField1 = 0;
}
[ プライベートフィールド ]
プライベートフィールドには、インスタンスの外部からアクセスすることができない。また、パブリックメソッド ( 後述 ) からもアクセスすることができない。
定義するには、コンストラクタ内で、varキーワードによる変数宣言を行う。また、コンストラクタの引数リストも、プライベートフィールドとなる。
function Hoge(arg1, arg2) // arg1とarg2もプライベートフィールド
{
var privateField1 = 0;
}
なお、プライベートフィールドは、 this.[ プライベートフィールド名 ] といった参照の仕方ができないので注意。参照するには直接プライベートフィールド名を記述する。
【 インスタンスメソッド 】
インスタンスメソッドは3種類に大別できる。
[ パブリックメソッド ]
パブリックメソッドには、インスタンスの外部からアクセスすることができる。また、パブリックメソッド内から、プライベートなメンバにアクセスすることはできない。
定義するには、コンストラクタのprototypeプロパティ ( 詳細はそのうち... ) に格納されているオブジェクトに、メソッドを追加する。
function Hoge()
{
}
Hoge.prototype.publicMethod1 =
function()
{
};
prototypeプロパティに格納されているオブジェクトに対してメソッドを追加するため、パブリックメソッドとなる関数オブジェクトは、ソースコードのロード時に一度だけ生成される。
[ プレビレッジドメソッド ]
プレビレッジドメソッドには、インスタンスの外部からアクセスすることができる。また、プレビレッジドメソッド内から、プライベートなメンバにアクセスすることができる。
定義するには、コンストラクタ内でインスタンスフィールドに関数オブジェクトを設定する。
function Hoge()
{
this.privilegedMethod1 =
function()
{
};
}
コンストラクタ内で自分自身のパブリックフィールドにメソッドを追加するので、プレビレッジドメソッドとなる関数オブジェクトは、コンストラクタ実行 ( インスタンス化 ) のたびに生成される。
[ プライベートメソッド ]
プライベートメソッドには、インスタンスの外部からアクセスすることはできない。また、プライベートメソッド内から、プライベートなメンバにアクセスすることができる。
定義するには、コンストラクタ内でプライベートフィールドに関数オブジェクトを設定する。
function Hoge()
{
var privateMethod1 =
function()
{
};
}
コンストラクタ内で自分自身のプライベートフィールドにメソッドを追加するので、プライベートメソッドとなる関数オブジェクトは、コンストラクタ実行 ( インスタンス化 ) のたびに生成される。
【 静的メソッド 】
静的メソッドは必ずパブリックになる。インスタンスフィールドやインスタンスメソッドにはアクセスすることができない。
定義するには、コンストラクタ関数自体のインスタンスフィールドに関数オブジェクトを設定する。前述したように、コンストラクタは、それ自体が ( 関数 ) オブジェクトである。
function Hoge()
{
}
Hoge.staticMethod1 =
function()
{
};
【 オブジェクトを直接表記 】
以前書いた記事を参照
【 参考 】
Collection & Copy - JavaScriptにおけるプライベートメンバ
オブジェクトは通常どおり、数値やBoolean値ならそのまま記述、文字列ならダブルクォーテーションで囲んで記述、配列なら[]の中にカンマ区切りでオブジェクトを記述、関数ならfunction(){}の形式で記述する。{[プロパティ名]: [オブジェクト], ...}の形式のオブジェクトも当然含ませることができる。
以下に例を記述。
オブジェクトを直接記述する例var obj1 =
{
p1: 1,
p2: "1",
p3:
[
{
p1: "a1",
p2: "a2"
},
{
p1: "b1",
p2: "b2"
}
],
m1:
function()
{
alert(this.p1 + this.p2);
},
m2:
function()
{
var message = "";
for (var i = 0; i < this.p3.length; i++)
{
message += this.p3[i].p1 + "," + this.p3[i].p2 + "¥r¥n";
}
alert(message);
},
m3:
function(arg1)
{
alert(arg1);
}
}
obj1.m1();
obj1.m2();
obj1.m3("test1");