2021年12月12日

F.prototype

new F() のように、コンストラクタ関数で新しいオブジェクトを作成できることを思い出してください。

F.prototype がオブジェクトの場合、new 演算子はそれを使用して新しいオブジェクトの [[Prototype]] を設定します。

ご注意ください

JavaScriptは最初からプロトタイプ継承を備えていました。それは言語の中核機能の1つでした。

しかし、昔はそれに直接アクセスする方法はありませんでした。確実に機能したのは、この章で説明するコンストラクタ関数の "prototype" プロパティだけでした。そのため、今でもそれを使用するスクリプトがたくさんあります。

ここで F.prototype は、F"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]]rabbitanimal から継承していることを意味する垂直矢印です。

new F 時のみに使用される F.prototype

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

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

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

すべての関数は、指定しなくても "prototype" プロパティを持っています。

デフォルトの "prototype" は、関数自体を指す constructor プロパティのみを持つオブジェクトです。

このように

function Rabbit() {}

/* default prototype
Rabbit.prototype = { constructor: Rabbit };
*/

確認できます

function Rabbit() {}
// by default:
// Rabbit.prototype = { constructor: Rabbit }

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

当然のことながら、何もしなければ、constructor プロパティは [[Prototype]] を介してすべてのウサギに利用可能です

function Rabbit() {}
// by default:
// Rabbit.prototype = { constructor: Rabbit }

let rabbit = new Rabbit(); // inherits from {constructor: Rabbit}

alert(rabbit.constructor == Rabbit); // true (from 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() {}

// Not overwrite Rabbit.prototype totally
// just add to it
Rabbit.prototype.jumps = true
// the default Rabbit.prototype.constructor is preserved

または、constructor プロパティを手動で再作成します

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

// now constructor is also correct, because we added it

まとめ

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

すべて非常にシンプルですが、明確にするためにいくつか注意点があります

  • F.prototype プロパティ ([[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" プロパティにアクセスすることでオブジェクトのコンストラクタを取得できます。

タスク

重要度: 5

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

最初に、次のコードがあります

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

let rabbit = new Rabbit();

alert( rabbit.eats ); // true
  1. 文字列をもう1つ追加しました (強調表示)。alert は jetzt 何を表示しますか?

    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 ); // ?

回答

  1. true.

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

  2. false.

    オブジェクトは参照によって割り当てられます。Rabbit.prototype からのオブジェクトは複製されず、Rabbit.prototyperabbit[[Prototype]] の両方から参照される単一のオブジェクトのままです。

    そのため、一方の参照を介してコンテンツを変更すると、もう一方の参照を介して表示されます。

  3. true.

    すべての delete 操作はオブジェクトに直接適用されます。ここで delete rabbit.eatsrabbit から eats プロパティを削除しようとしますが、それはありません。そのため、操作は何も効果がありません。

  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 を上書きし、constructorUser を参照するように再作成するのを忘れた場合、失敗します。

例えば

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. まず、userconstructor を探します. 見つかりません.
  2. 次に、プロトタイプチェーンをたどります。user のプロトタイプは User.prototype ですが、constructor もありません (正しく設定するのを「忘れた」ためです!)。
  3. チェーンをさらに上に進むと、User.prototype はプレーンオブジェクトであり、そのプロトタイプは組み込みの Object.prototype です。
  4. 最終的に、組み込みの Object.prototype には、組み込みの Object.prototype.constructor == Object があります。そのため、これが使用されます。

最終的に、最後は let user2 = new Object('Pete') になります。

おそらく、これは私たちが望んでいることではありません。new Object ではなく、new User を作成したいと思います。これが constructor がないことの結果です。

(ちなみに、new Object(...) 呼び出しは引数をオブジェクトに変換します。これは理論上のことであり、実際には誰も値を指定して new Object を呼び出すことはなく、一般的にオブジェクトを作成するために new Object を使用することはありません)。

チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善のための提案がある場合は、コメントする代わりに、GitHubのissue またはプルリクエストを送信してください。
  • 記事の内容が理解できない場合は、詳しく説明してください。
  • 数語のコードを挿入するには、<code> タグを使用し、複数行の場合は <pre> タグで囲み、10行を超える場合はサンドボックス (plnkrjsbincodepen…) を使用してください。