2022年7月6日

ネイティブプロトタイプ

"prototype" プロパティは、JavaScript 自体のコアによって広く使用されています。すべての組み込みコンストラクター関数はそれを使用します。

最初に詳細を見てから、組み込みオブジェクトに新しい機能を追加するためにどのように使用するかを見ていきます。

Object.prototype

空のオブジェクトを出力するとしましょう。

let obj = {};
alert( obj ); // "[object Object]" ?

文字列 "[object Object]" を生成するコードはどこにあるのでしょうか?それは組み込みの toString メソッドですが、どこにあるのでしょうか?obj は空です!

…しかし、短い記法 obj = {}obj = new Object() と同じです。ここで Object は独自の prototype を持つ組み込みオブジェクトコンストラクター関数で、toString やその他のメソッドを持つ巨大なオブジェクトを参照しています。

これが何が起こっているかです。

new Object() が呼び出されると(またはリテラルオブジェクト {...} が作成されると)、前の章で議論したルールに従って、その [[Prototype]]Object.prototype に設定されます。

したがって、obj.toString() が呼び出されると、メソッドは Object.prototype から取得されます。

このように確認できます。

let obj = {};

alert(obj.__proto__ === Object.prototype); // true

alert(obj.toString === obj.__proto__.toString); //true
alert(obj.toString === Object.prototype.toString); //true

Object.prototype の上にチェーンされた [[Prototype]] がもうないことに注意してください。

alert(Object.prototype.__proto__); // null

その他の組み込みプロトタイプ

ArrayDateFunction などの他の組み込みオブジェクトも、メソッドをプロトタイプに保持します。

たとえば、配列 [1, 2, 3] を作成すると、デフォルトの new Array() コンストラクターが内部的に使用されます。したがって、Array.prototype がそのプロトタイプになり、メソッドを提供します。これは非常にメモリ効率が良いです。

仕様により、すべての組み込みプロトタイプは最上位に Object.prototype を持っています。そのため、「すべてはオブジェクトから継承する」と言う人がいます。

これが全体図です(3つの組み込みオブジェクトが収まるように)。

プロトタイプを手動で確認してみましょう。

let arr = [1, 2, 3];

// it inherits from Array.prototype?
alert( arr.__proto__ === Array.prototype ); // true

// then from Object.prototype?
alert( arr.__proto__.__proto__ === Object.prototype ); // true

// and null on the top.
alert( arr.__proto__.__proto__.__proto__ ); // null

プロトタイプの一部のメソッドは重複する可能性があります。たとえば、Array.prototype は、カンマ区切りの要素をリストする独自の toString を持っています。

let arr = [1, 2, 3]
alert(arr); // 1,2,3 <-- the result of Array.prototype.toString

以前見たように、Object.prototype にも toString がありますが、Array.prototype がチェーン内でより近いため、配列のバリアントが使用されます。

Chrome 開発者コンソールなどのブラウザツールも継承を示します (組み込みオブジェクトには console.dir の使用が必要な場合があります)。

他の組み込みオブジェクトも同じように動作します。関数でさえ、組み込みの Function コンストラクターのオブジェクトであり、そのメソッド (call / apply など) は Function.prototype から取得されます。関数には独自の toString もあります。

function f() {}

alert(f.__proto__ == Function.prototype); // true
alert(f.__proto__.__proto__ == Object.prototype); // true, inherit from objects

プリミティブ

最も複雑なことは、文字列、数値、およびブール値で発生します。

覚えているように、それらはオブジェクトではありません。しかし、それらのプロパティにアクセスしようとすると、組み込みコンストラクター StringNumberBoolean を使用して一時的なラッパーオブジェクトが作成されます。それらはメソッドを提供し、消えます。

これらのオブジェクトは私たちには見えないように作成され、ほとんどのエンジンはそれらを最適化しますが、仕様はまさにこのように記述しています。これらのオブジェクトのメソッドもプロトタイプに存在し、String.prototypeNumber.prototypeBoolean.prototype として利用できます。

null および undefined にはオブジェクトラッパーはありません

特別な値 nullundefined は別です。それらにはオブジェクトラッパーがないため、メソッドとプロパティは利用できません。また、対応するプロトタイプもありません。

ネイティブプロトタイプの変更

ネイティブプロトタイプは変更できます。たとえば、String.prototype にメソッドを追加すると、すべての文字列で利用できるようになります。

String.prototype.show = function() {
  alert(this);
};

"BOOM!".show(); // BOOM!

開発プロセス中に、組み込みメソッドに追加したい新しいメソッドのアイデアが生まれる可能性があり、それらをネイティブプロトタイプに追加したいと思うかもしれません。しかし、それは一般的に悪い考えです。

重要

プロトタイプはグローバルであるため、競合が発生しやすいです。2つのライブラリがメソッド String.prototype.show を追加した場合、それらの1つが他方のメソッドを上書きします。

したがって、一般的に、ネイティブプロトタイプの変更は悪い考えであると考えられています。

現代のプログラミングでは、ネイティブプロトタイプの変更が承認されるケースは1つだけです。それはポリフィルです。

ポリフィルとは、JavaScript仕様に存在するが、特定のJavaScriptエンジンでまだサポートされていないメソッドの代替を作成するための用語です。

次に、手動で実装し、組み込みプロトタイプにそれを追加できます。

if (!String.prototype.repeat) { // if there's no such method
  // add it to the prototype

  String.prototype.repeat = function(n) {
    // repeat the string n times

    // actually, the code should be a little bit more complex than that
    // (the full algorithm is in the specification)
    // but even an imperfect polyfill is often considered good enough
    return new Array(n + 1).join(this);
  };
}

alert( "La".repeat(3) ); // LaLaLa

プロトタイプからの借用

デコレーターと転送、call/apply の章では、メソッドの借用について説明しました。

これは、あるオブジェクトからメソッドを取得して別のオブジェクトにコピーする場合です。

ネイティブプロトタイプの一部のメソッドは、しばしば借用されます。

たとえば、配列のようなオブジェクトを作成する場合、いくつかの Array メソッドをそれにコピーしたい場合があります。

例。

let obj = {
  0: "Hello",
  1: "world!",
  length: 2,
};

obj.join = Array.prototype.join;

alert( obj.join(',') ); // Hello,world!

組み込みの join メソッドの内部アルゴリズムは、正しいインデックスと length プロパティのみを気にするため、機能します。オブジェクトが実際に配列であるかどうかはチェックしません。多くの組み込みメソッドがそのようになっています。

もう1つの可能性は、obj.__proto__Array.prototype に設定して継承することです。これにより、すべての Array メソッドが obj で自動的に利用可能になります。

ただし、obj がすでに別のオブジェクトから継承している場合は不可能です。一度に継承できるオブジェクトは1つだけであることを忘れないでください。

メソッドの借用は柔軟性があり、必要に応じて異なるオブジェクトの機能を組み合わせることができます。

まとめ

  • すべての組み込みオブジェクトは同じパターンに従います。
    • メソッドはプロトタイプ (Array.prototypeObject.prototypeDate.prototype など) に保存されます。
    • オブジェクト自体はデータ (配列項目、オブジェクトプロパティ、日付) のみを保存します。
  • プリミティブもラッパーオブジェクトのプロトタイプにメソッドを保存します:Number.prototypeString.prototypeBoolean.prototypeundefinednull のみにはラッパーオブジェクトがありません。
  • 組み込みプロトタイプは、変更したり、新しいメソッドを追加したりできます。ただし、それらを変更することはお勧めしません。唯一許容されるケースは、おそらく、新しい標準を追加する場合ですが、まだJavaScriptエンジンでサポートされていません。

タスク

重要度: 5

すべての関数のプロトタイプに、ms ミリ秒後に関数を実行するメソッド defer(ms) を追加します。

実行後、次のようなコードが動作するはずです。

function f() {
  alert("Hello!");
}

f.defer(1000); // shows "Hello!" after 1 second
Function.prototype.defer = function(ms) {
  setTimeout(this, ms);
};

function f() {
  alert("Hello!");
}

f.defer(1000); // shows "Hello!" after 1 sec
重要度: 4

すべての関数のプロトタイプに、呼び出しを ms ミリ秒遅らせるラッパーを返すメソッド defer(ms) を追加します。

これがどのように動作するかの例です。

function f(a, b) {
  alert( a + b );
}

f.defer(1000)(1, 2); // shows 3 after 1 second

引数は元の関数に渡す必要があることに注意してください。

Function.prototype.defer = function(ms) {
  let f = this;
  return function(...args) {
    setTimeout(() => f.apply(this, args), ms);
  }
};

// check it
function f(a, b) {
  alert( a + b );
}

f.defer(1000)(1, 2); // shows 3 after 1 sec

注意してください: オブジェクトメソッドでデコレーションが動作するように、f.applythis を使用します。

したがって、ラッパー関数がオブジェクトメソッドとして呼び出された場合、this は元のメソッド f に渡されます。

Function.prototype.defer = function(ms) {
  let f = this;
  return function(...args) {
    setTimeout(() => f.apply(this, args), ms);
  }
};

let user = {
  name: "John",
  sayHi() {
    alert(this.name);
  }
}

user.sayHi = user.sayHi.defer(1000);

user.sayHi();
チュートリアルマップ

コメント

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