JavaScript 〜 コンストラクタ関数について復習

最初に、コンストラクタ関数とは、「オブジェクトのプロパティを初期化する関数で、new演算子と一緒に使われる」とJavaScript第5版に書いてあった。
new演算子が新しいオブジェクトを生成し、そのオブジェクトをthisキーワードの値に設定した後、コンストラクタ関数を呼び出す。

//コンストラクタ関数の定義
var Person = function(name){
  this.name = name;
  this.say = function(){
     return "名前は" + this.name;
  };
};

//コンストラクタ関数の実行
var bob = new Person('bob');

//メソッド実行
var word = bob.say();

//「名前はbob」と表示
alert(word);

Personは単なる関数なので、次のように呼び出しても変わらないと思うかもしれないが、これはイケナイ。
new演算子を使用しない場合、コンストラクタ関数内部のthisはグローバルオブジェクトを指してしまう。new演算子を使用せずにコンストラクタ関数を呼び出すと、コンストラクタ関数がthisをreturnしてくれないので、
bobはundefinedとなり、bob.say()はエラーとなる*1

//コンストラクタ関数の定義
var Person = function(name){
  this.name = name;
  this.say = function(){
     return "名前は" + this.name;
  };
};

//new演算子を使用せずに関数として実行
var bob = Person('bob');

//メソッド実行
//bobはundefinedなのでエラー
var word = bob.say();

つまり、コンストラクタ関数をnew演算子を使用して呼び出すと、下記コメント部分の処理が関数に定義されて、関数が実行される。

var Person = function(name){
  // 新しいオブジェクトを作成
  // var this = {};

 //オブジェクトにプロパティをセット
  this.name = name;
  this.say = function(){
     return "名前は" + this.name;
  };

 //オブジェクトを返却
  // return this;
};

だったら、最初からこのように実装すればよいではないか、というのが予約語thisの代わりに明示的に変数を用意して、それをthisの代わりに使用する実装。

var Person = function(name){
  // 新しいオブジェクトを作成
  var that = {};

 //オブジェクトにプロパティをセット
  that.name = name;
  that.say = function(){
     return "名前は" + this.name;
  };

 //オブジェクトを返却
  return that;
};

常にこの実装方法にすればよいではないか、と思ったが、このパターンではプロトタイプへのリンクが失われてしまう。何が困るのか。

例えば、Person関数のプロパティsayは、Person毎に実行結果は変われど処理内容が変わるものではない。なので、コンストラクタ関数が呼ばれるたびにメモリに新しい関数が作成されることを避けるために、コンストラクタ関数内に定義するのではなく、prototypeプロパティを使用する。

var Person = function(name){
 //オブジェクトにプロパティをセット
  this.name = name;
};

Person.prototype.say = function(){
     return "名前は" + this.name;
};

//コンストラクタ関数の実行
var bob = new Person("bob");

//「名前はbob」と表示
alert(bob.say());

しかし、Personのコンストラクタ関数を、thatを使用した書き方にすると、bob.say()は実行されない

var Person = function(name){
  // 新しいオブジェクトを作成
  var that = {};

 //オブジェクトにプロパティをセット
  that.name = name;
  that.say = function(){
     return "名前は" + this.name;
  };

 //オブジェクトを返却
  return that;
};

Person.prototype.say = function(){
     return "名前は" + this.name;
};

//コンストラクタ関数の実行
var bob = new Person("bob");

//prototypeプロパティへのリンクが失われてしまうので何も表示されない
alert(bob.say());

それでは、結局どのような実装にすれば良いのか。
関数内で、thisが自分自身と等しいかをinstanceofを使用して判定する。等しい場合はnew演算子を使用して関数が実行されている場合なので、そのままthisを使用する。等しくない場合は、new演算子を実行して自分自身を呼び出す。

var Person = function Person(name){
  //new演算子を使用して関数実行された場合
  if(this instanceof Person){
   this.name = name;
  }else{
   return new Person(name);
  } 
};

Person.prototype.say = function(){
  return "名前は" + this.name;
};
 
var bob = new Person("bob");
alert(bob.say());
 
var alice = Person("alice");
alert(alice.say());

*1:new演算子を省略した場合、thisはwindowとなる。そのため、window.say()の結果は、「名前はbob」となる。