2022年3月13日

F.prototype

思い出してください、新しいオブジェクトは new F() のように、コンストラクタ関数で生成できます。

F.prototype がオブジェクトの場合、new 演算子は新しいオブジェクトで [[Prototype]] をセットするためにそれを使用します。

注意:

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

しかし、昔は直接アクセスすることはできませんでした。確実に機能したのは、この章で説明する、コンストラクタ関数の "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.prototypenew F 時にだけ使用されます

F.prototype プロパティは new F が呼ばれたときにだけ使用され、新しいオブジェクトの [[Prototype]] を割り当てます。

作成後に、F.prototype プロパティが変更された場合(F.prototype = <別のオブジェクト>)、new F によって生成された新しいオブジェクトは [[Prototype]] として別のオブジェクトを持ちますが、既に存在するオブジェクトは古いものを保持したままです。

デフォルトの 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");

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

しかし、おそらく "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 で持っているものです。

チュートリアルマップ