構造関数とnewコマンド

25693 ワード

JavaScript言語はオブジェクト指向プログラミング能力が強いので、本章ではJavaScriptがどのようにオブジェクト指向プログラミングを行うかを紹介します.
相手は何ですか
オブジェクト指向プログラミング(Object Oriented Prograamming)は、現在主流のプログラミングモデルです.それは真実な世界の各種の複雑な関係を抽象的に一つの対象として、そして対象の間の分業と協力によって、真実な世界に対するシミュレーションを完成します.
すべての対象は機能センターであり、明確な分業があり、情報の受け取り、データの処理、情報の発信などの任務を遂行することができます.したがって、オブジェクト指向プログラミングは、柔軟、コード多重、高度モジュール化などの特徴があり、維持と開発が容易であり、一連の関数やコマンドからなる伝統的なプロセスプログラミングよりも、複数の人が協力する大型ソフトウェアプロジェクトに適している.
では、「対象」はいったい何ですか?
私たちは二つのレベルから理解します.
(1)対象は単体の抽象的なものである.
本、自動車、一人が対象となります.データベース、ウェブページ、リモートサーバとの接続も対象となります.実物が抽象的に対象になると、実物の関係は対象との関係になり、現実の状況をシミュレーションし、対象に対してプログラミングすることができます.
(2)オブジェクトは、属性と方法をカプセル化した容器である.
属性は対象の状態です.方法は対象の行為です.例えば、私たちは動物をanimalの対象として抽象してもいいです.「属性」を使って具体的にはその動物です.「方法」を使って動物のある行為(走る、狩り、休憩など)を表します.
構造関数
オブジェクト指向プログラミングの第一歩は、オブジェクトを生成することです.
前に述べましたが、対象は単体の抽象です.通常はテンプレートが必要で、ある種の実物の共通の特徴を表し、対象はこのテンプレートに基づいて生成される.
典型的なオブジェクト指向プログラミング言語(例えばC++とJava)には、クラスという概念があります.クラスとは、オブジェクトのテンプレートのことで、オブジェクトはクラスの例です.しかし、JavaScript言語の対象システムは、「クラス」に基づくものではなく、構造関数とプロトタイプチェーンに基づくものです.
JavaScript言語は、オブジェクトのテンプレートとして構造関数を使用します.「コンストラクタ」とは、オブジェクトを生成するための関数です.オブジェクトの基本構造を記述するテンプレートを提供します.一つのコンストラクタは、複数のオブジェクトを生成できます.これらのオブジェクトは同じ構造を持っています.
構造関数の書き方は普通の関数ですが、自分の特徴と使い方があります.
var Vehicle = function () {
  this.price = 1000;
};
上記のコードでは、Vehicleは構成関数であり、テンプレートを提供してインスタンスオブジェクトを生成する.普通の関数と区別するために、コンストラクションの名前の最初の文字は通常大文字です.
構造関数の特徴は二つあります.
関数内部にthisのキーワードが使用され、生成されるオブジェクトの例を表しています.オブジェクトを生成するには、newコマンドを使用して、Vehicle関数を呼び出す必要があります.newコマンド
基本的な使い方newコマンドの役割は、構造関数を実行し、インスタンスオブジェクトを返すことである.
var Vehicle = function () {
  this.price = 1000;
};

var v = new Vehicle();
v.price // 1000
上記のコードは、newコマンドにより、構成関数Vehicleにインスタンスオブジェクトを生成させ、変数vに保存させる.この新たに生成された例示的なオブジェクトは、構造関数Vehicleからpriceの属性を継承する.newコマンドが実行されると、構造関数内部のthisは、新たに生成されたインスタンスオブジェクトを表し、this.priceは、インスタンスオブジェクトにprice属性があり、値は1000であることを示している.newコマンドを使用する場合、必要に応じて、コンストラクタはパラメータを受け取ることができます.
var Vehicle = function (p) {
  this.price = p;
};

var v = new Vehicle(500);
newコマンド自体はコンストラクタを実行することができますので、後のコンストラクタは括弧を持ってもいいし、括弧を付けなくてもいいです.次の2行のコードは等価です.
var v = new Vehicle();
var v = new Vehicle;
自然な問題ですが、newコマンドを使用することを忘れたら、直接コンストラクタを呼び出すとどうなりますか?
この場合,コンストラクタは普通の関数になり,インスタンスオブジェクトは生成されない.また、後述する原因により、thisは、グローバルオブジェクトを代表して、いくつかの予期せぬ結果をもたらす.
var Vehicle = function (){
  this.price = 1000;
};

var v = Vehicle();
v.price
// Uncaught TypeError: Cannot read property 'price' of undefined

price
// 1000
上のコードでVehicleコンストラクタを呼び出すとnewコマンドを追加するのを忘れました.その結果、price属性はグローバル変数になり、vundefinedになる.
したがって、newコマンドを使用しないで、直接的に構造関数を呼び出すことを避けるために、非常に注意すべきである.構造関数がnewコマンドと一緒に使用される必要があることを保証するために、1つの解決法は、構造関数の内部で厳格なモード、すなわち第1行にuse strictを加えることである.
function Fubar(foo, bar){
  'use strict';
  this._foo = foo;
  this._bar = bar;
}

Fubar()
// TypeError: Cannot set property '_foo' of undefined
上のコードのFubarは構成関数であり、use strictコマンドはこの関数が厳密なモードで動作することを保証する.厳密なモードでは、関数内部のthisは大域オブジェクトに向けられず、デフォルトはundefinedに等しいため、newを追加しないとエラーメッセージを呼び出すことになる(JavaScriptはundefinedに属性を追加することができない).
もう一つの解決策は、newコマンドが使用されているかどうかをコンストラクタ内部で判断し、使用されていないことが発見された場合、直接にインスタンスオブジェクトに戻ることである.
function Fubar(foo, bar) {
  if (!(this instanceof Fubar)) {
    return new Fubar(foo, bar);
  }

  this._foo = foo;
  this._bar = bar;
}

Fubar(1, 2)._foo // 1
(new Fubar(1, 2))._foo // 1
上のコードの構造関数は、newコマンドを加えなくても同じ結果が得られます.
new命令の原理newコマンドを使用すると、その後の関数呼び出しは正常な呼び出しではなく、次のステップを順次実行します.
返す対象として空のオブジェクトを作成します.この空のオブジェクトのプロトタイプを、構造関数のprototype属性に向ける.
この空のオブジェクトを関数内部のthisキーワードに割り当てます.
コンストラクタ内部のコードの実行を開始します.
すなわち、構造関数の内部で、thisは新たに生成された空のオブジェクトを指し、thisの動作のすべては、この空のオブジェクト上で発生する.コンストラクタを「コンストラクター」というのは、この関数の目的は、空のオブジェクト(すなわちthisオブジェクト)を操作して、その「構造」を必要とする形です.
コンストラクタ内部にreturn文があり、returnの後にオブジェクトが続く場合、newコマンドはreturn文で指定されたオブジェクトに戻ります.そうでなければ、return文にかかわらず、thisオブジェクトに戻る.
var Vehicle = function () {
  this.price = 1000;
  return 1000;
};

(new Vehicle()) === 1000
// false
上記のコードにおいて、構造関数Vehiclereturn文は数値を返します.このとき、newコマンドは、このreturn文を無視して、「構造」後のthisオブジェクトに戻る.
しかし、return文がthisとは無関係の新しいオブジェクトを返した場合、newコマンドはthisオブジェクトではなく、この新しいオブジェクトを返します.この点には特に注意が必要です.
var Vehicle = function (){
  this.price = 1000;
  return { price: 2000 };
};

(new Vehicle()).price
// 2000
上記のコードにおいて、構造関数Vehiclereturn文は、新しいオブジェクトを返します.newコマンドは、thisオブジェクトではなく、このオブジェクトに戻ります.
一方、一般関数(内部にthisキーの関数がない)にnewコマンドを使用すると、空のオブジェクトが返されます.
function getMessage() {
  return 'this is a message';
}

var msg = new getMessage();

msg // {}
typeof msg // "object"
上記のコードでは、getMessageは普通関数であり、文字列を返します.newコマンドを使用すると、空のオブジェクトが得られます.これは、newコマンドが常にオブジェクトに戻るか、インスタンスオブジェクトか、またはreturn文で指定されたオブジェクトになるからです.本例では、return文は文字列を返しているので、newコマンドはこの文を無視しています.newコマンドの簡略化された内部フローは、以下のコードで表されてもよい.
function _new(/*      */ constructor, /*        */ param1) {
  //   arguments       
  var args = [].slice.call(arguments);
  //       
  var constructor = args.shift();
  //        ,        prototype   
  var context = Object.create(constructor.prototype);
  //       
  var result = constructor.apply(context, args);
  //          ,     ,    context   
  return (typeof result === 'object' && result != null) ? result : context;
}

//   
var actor = _new(Person, '  ', 28);
new.target
関数内部ではnew.target属性が使用できます.現在の関数がnewコマンド呼び出しの場合、new.targetは現在の関数を指し、そうでなければundefinedです.
function f() {
  console.log(new.target === f);
}

f() // false
new f() // true
この属性を使用して、関数が起動したときにnewコマンドが使用されているかどうかを判断することができます.
function f() {
  if (!new.target) {
    throw new Error('    new     !');
  }
  // ...
}

f() // Uncaught Error:     new     !
上のコードでは、構造関数fが呼び出したときにnewコマンドを使用していないので、エラーを投げました.
Object.creat()を使ってインスタンスオブジェクトを作成します.
テンプレートとしてのコンストラクタは、インスタンスオブジェクトを生成することができます.しかし、場合によっては、インスタンスオブジェクトしか入手できないが、オブジェクトは基本的に構造関数によって生成されていないので、Object.create()方法を使用して、あるインスタンスオブジェクトを直接テンプレートとして使用して、新しいインスタンスオブジェクトを生成することができる.
var person1 = {
  name: '  ',
  age: 38,
  greeting: function() {
    console.log('Hi! I\'m ' + this.name + '.');
  }
};

var person2 = Object.create(person1);

person2.name //   
person2.greeting() // Hi! I'm   .
上記のコードにおいて、オブジェクトperson1person2のテンプレートであり、後者は前者の属性および方法を継承する.