JavaScriptのコンストラクタは色々ある他のプログラム言語とは一味違います。newキーワードが付いているどんな関数呼び出しも、コンストラクタとして機能します。
コンストラクタ内部では -呼び出された関数の事です- thisの値は新規に生成されたObjectを参照しています。この新規のオブジェクトのprototypeは、コンストラクタとして起動した関数オブジェクトのprototypeとして設定されています。
もし呼び出された関数が、returnステートメントを明示していない場合は、暗黙の了解でthisの値を -新規のオブジェクトとして- 返します。
function Foo() {
this.bla = 1;
}
Foo.prototype.test = function() {
console.log(this.bla);
};
var test = new Foo();
上記でFooはコンストラクタとして呼び出され、Foo.prototypeとして新規に生成されたprototypeを設定されています。
明示的にreturnステートメントがある場合、Objectの値を返すだけでなく関数はこのステートメントを返します。
function Bar() {
return 2;
}
new Bar(); // 新しいオブジェクト
function Test() {
this.value = 2;
return {
foo: 1
};
}
new Test(); // 返ってきたオブジェクト
newキーワードが省略されている場合は、関数は新しいオブジェクトを返す事はありません。
function Foo() {
this.bla = 1; // グローバルオブジェクトに設定される
}
Foo(); // undefinedが返る
上記の例では、いくつかのケースでは動作するように見える場合があります。JavaScriptのthisの働きのせいで、グローバルオブジェクトがthisの値として使用されるからです。
newキーワードを省略するためには、コンストラクタ関数が明示的に値を返す必要があります。
function Bar() {
var value = 1;
return {
method: function() {
return value;
}
}
}
Bar.prototype = {
foo: function() {}
};
new Bar();
Bar();
Barで呼び出されたものは両方とも全く同じものものになります。これには、methodと呼ばれるプロパティを持ったオブジェクトが新しく生成されますが、これはクロージャです。
また、注意する点として呼び出されたnew Bar()は返ってきたオブジェクトのプロトタイプに影響しません。プロトタイプが新しく生成されたオブジェクトにセットされるまで、Barは絶対に新しいオブジェクトを返さないのです。
上記の例では、newキーワードの使用の有無は機能的に違いがありません。
大半の場合に推奨されるのは、newの付け忘れによるバグを引き起こしやすいので使用しない事です。
新しいオブジェクトを作成するためにファクトリーを使用するか、そのファクトリー内部に新しいオブジェクトを構築する必要があります。
function Foo() {
var obj = {};
obj.value = 'blub';
var private = 2;
obj.someMethod = function(value) {
this.value = value;
}
obj.getPrivate = function() {
return private;
}
return obj;
}
上記の例ではnewキーワードが無いため堅牢になりますし、確実にプライベート変数を使用するのが簡単になりますが、いくつかの欠点があります。
- 作られたオブジェクトがプロトタイプ上のメソッドを共有しないために、よりメモリーを消費してしまいます。
- ファクトリーを継承するために、他のオブジェクトの全てのメソッドをコピーする必要があるか、新しいオブジェクトのプロトタイプ上にそのオブジェクトを設置する必要があります。
newキーワードが無いという理由だけで、プロトタイプチェーンから外れてしまうのは、どことなく言語の精神に反します。
newキーワードが省略される事により、バグの可能性がもたらされますがプロトタイプを使わない確実な理由にはなりません。最終的には、アプリケーションの必要性により、どちらの解決法がより良いかが決まってきます。特に大切なのは、オブジェクトの作成に特定のスタイルを選ぶ事、またそのスタイルに固執する事です。