最新のJavaScriptでは、前の記事で説明したとおり、__proto__ を使ってプロトタイプをセットすることができます。しかし、それはいつでもそうではありませんでした。

JavaScriptは最初からプロトタイプの継承を持っています。 それは言語の中心的な特徴の1つでした。

しかし、以前は別(であり唯一)の方法がありました。: コンストラクタ関数の "prototype" プロパティを使うことです。そして、それを使っているスクリプトはまだたくさんあります。

“prototype” プロパティ

すでにご存知の通り、new F() は新しいオブジェクトを作ります。

new F() で新しいオブジェクトが作られるとき、オブジェクトの [[Prototype]]F.prototype にセットされます。

言い換えると、もし F がオブジェクト型の値をもつ prototype プロパティを持っている場合、new 演算子は新しいオブジェクトに対してそれを [[Prototype]] にセットします。

ここで F.prototypeF 上の "prototype" と名付けられた通常のプロパティを意味していることに注意してください。用語 “プロトタイプ” と似ていますが、ここでは本当にその名前をもつ通常のプロパティを意味しています。

ここではその例です:

let animal = {
  eats: true
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = animal;

let rabbit = new Rabbit("White Rabbit"); //  rabbit.__proto__ == animal

alert( rabbit.eats ); // true

Rabbit.prototype = animal の設定は、文字通り次のことを述べています。: "new Rabbit が生成される時、その [[Prototype]]animal を割り当てます。"

これが結果のイメージです:

イメージ上で、"prototype" は水平矢印で、それは通常のプロパティです。[[Prototype]] は縦矢印で、animal から rabbit の継承を意味しています。

デフォルトの F.prototype, constructor プロパティ

すべての関数は、たとえ明示的に提供されていなくても "prototype" プロパティを持っています。

デフォルトの "prototype"constructor というプロパティだけを持つオブジェクトで、それは関数自体を指します。

こんな感じです:

function Rabbit() {}

/* デフォルト prototype
Rabbit.prototype = { constructor: Rabbit };
*/

コードでそれを確認できます:

function Rabbit() {}
// デフォルトでは:
// Rabbit.prototype = { constructor: Rabbit }

alert( Rabbit.prototype.constructor == Rabbit ); // true

当然、何もしない場合、 constructor プロパティは [[Prototype]] を通じてすべての rabbit が利用できます。:

function Rabbit() {}
// デフォルトでは:
// Rabbit.prototype = { constructor: Rabbit }

let rabbit = new Rabbit(); // {constructor: Rabbit} の継承

alert(rabbit.constructor == Rabbit); // true (prototype から)

constructor プロパティを使って既存のものと同じコンストラクタを使って新しいオブジェクトを作成することができます。

このように:

function Rabbit(name) {
  this.name = name;
  alert(name);
}

let rabbit = new Rabbit("White Rabbit");

let rabbit2 = new rabbit.constructor("Black Rabbit");

これは、オブジェクトを持っているが、どのコンストラクタが使われたか分からない場合(例えば3rdパーティライブラリが使われているなど)で、同じ種類のものを使って別のオブジェクトを作る必要がある場合に便利です。

しかし、おそらく "constructor" に関する最も重要なことは…

…JavaScript 自体は正しい "constructor" 値を保証しません。

はい、関数のためのデフォルトの "prototype" は存在しますが、それがすべてです。その後どうなるかは私たち次第です。

特に、もしデフォルトプロトタイプ全体を置き換えると、その中に "constructor" はなくなります。

例:

function Rabbit() {}
Rabbit.prototype = {
  jumps: true
};

let rabbit = new Rabbit();
alert(rabbit.constructor === Rabbit); // false

したがって、正しい "constructor" を維持するためには、全体を上書きする代わりに、デフォルト "prototype" に対して追加/削除を行います。:

function Rabbit() {}

// 完全に Rabbit.prototype を上書きはしません
// 単に追加するだけです
Rabbit.prototype.jumps = true
// デフォルト Rabbit.prototype.constructor は保持されます

もしくは、代替として手動で constructor プロパティを再び作ります。:

Rabbit.prototype = {
  jumps: true,
  constructor: Rabbit
};

// 追加したので、これで constructor も正しいです

サマリ

このチャプターでは、constructor 関数を通して作成されたオブジェクトのための [[Prototype]] を設定方法について簡単に説明しました。後で、それに依存するより高度なプログラミングパターンを見ていきます。

すべてが非常にシンプルで、物事を明確にするための留意事項はほんの少しです。:

  • F.prototype プロパティは [[Prototype]] と同じではありません。F.prototype がする唯一のことは: new F() が呼ばれたときに新しいオブジェクトの [[Prototype]] をセットすることです。
  • F.prototype の値はオブジェクトまたは null でなければなりません。: 他の値では動作しません。
  • "prototype" プロパティはコンストラクタ関数に設定され、new で呼び出されたときにのみ、特別な効果があります。

通常のオブジェクトでは、prototype は特別なものではありません。:

let user = {
  name: "John",
  prototype: "Bla-bla" // no magic at all
};

デフォルトでは、すべての関数は F.prototype = { constructor: F } を持っているので、その "constructor" プロパティへアクセスすることで、オブジェクトの constructor を取得することができます。

タスク

重要性: 5

下のコードでは、new Rabbit を作り、そのプロトタイプを変更しようとしています。

最初は次のコードがあります:

function Rabbit() {}
Rabbit.prototype = {
  eats: true
};

let rabbit = new Rabbit();

alert( rabbit.eats ); // true
  1. 1つ文字列を追加しました(強調部分)。今 alert は何が表示されるでしょう?

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    Rabbit.prototype = {};
    
    alert( rabbit.eats ); // ?
  2. …また、コードが次のような場合は(1行置き換えました)?

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    Rabbit.prototype.eats = false;
    
    alert( rabbit.eats ); // ?
  3. この場合は (1行置き換えました)?

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    delete rabbit.eats;
    
    alert( rabbit.eats ); // ?
  4. 最後のバリアントです:

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    delete Rabbit.prototype.eats;
    
    alert( rabbit.eats ); // ?

Answers:

  1. true.

    Rabbit.prototype への代入は、新しいオブジェクトに対して [[Prototype]] を設定しますが、既存のものへの影響はありません。

  2. false.

    オブジェクトは参照によって代入されます。Rabbit.prototype からのオブジェクトは複製されておらず、依然として、Rabbit.prototyperabbit[[Prototype]] 両方によって参照される1つのオブジェクトです。

    従って、1つの参照を通してその中身を変えたとき、別の参照と通じてそれが見えます。

  3. true.

    すべての delete 操作はオブジェクトに対して直接適用されます。今回の delete rabbit.eatsrabbit から eats プロパティを削除しようとしますが、rabbit は持ってないのでこの操作は何の影響も与えません。

  4. undefined.

    プロトタイプから eats プロパティが削除されたので、もう存在していません。

重要性: 5

想像してください、コンストラクタ関数によって作成された任意のオブジェクト obj があります – 今、それを使って新しいオブジェクトを作りたいです。

私たちは、このようにすることができるでしょうか?

let obj2 = new obj.constructor();

このコードを正しく動作させる obj のコンストラクタ関数の例を提示してください。そして、間違って動作する例も提示してください。

もし "constructor" プロパティが正しい値を持っていることが確かであるなら、私たちはこのようなアプローチを使うことができます。

例えば、デフォルトの "prototype" を触らないのであれば、このコードは確実に動作します:

function User(name) {
  this.name = name;
}

let user = new User('John');
let user2 = new user.constructor('Pete');

alert( user2.name ); // Pete (worked!)

User.prototype.constructor == User であるため、これは動作します。

…しかし、いわば誰かが User.prototype を上書きし、"constructor" を再作成するのを忘れている場合、それは失敗するでしょう。

例:

function User(name) {
  this.name = name;
}
User.prototype = {}; // (*)

let user = new User('John');
let user2 = new user.constructor('Pete');

alert( user2.name ); // undefined

なぜ user2.nameundefined なのでしょう?

ここで、new user.constructor('Pete') は次のように動作します:

  1. 最初に、user の中で constructor を探します。ありません。
  2. 次に、プロトタイプチェーンに沿います。user のプロトタイプは User.prototype で、これも constructor を持っていません。
  3. User.prototype の値は普通のオブジェクト {} であり、そのプロトタイプは Object.prototype です。そして、Object.prototype.constructor == Object があります。なので、これが使われます。

最終的に、let user2 = new Object('Pete') となります。組み込みの Object コンストラクタは引数を無視し、常に空のオブジェクトを生成します – これは、結局私たちが user2 で持っているものです。

チュートリアルマップ

コメント

コメントをする前に読んでください…
  • 自由に記事への追加や質問を投稿をしたり、それらに回答してください。
  • 数語のコードを挿入するには、<code> タグを使ってください。複数行の場合は <pre> を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。
  • 記事の中で理解できないことがあれば、詳しく説明してください。