"prototype"
プロパティは、JavaScript 自体のコアによって広く使用されています。すべての組み込みコンストラクター関数はこれを使用します。
まず詳細を見て、次にそれを使用して組み込みオブジェクトに新しい機能を追加する方法を見ていきます。
空のオブジェクトを出力するとします。
obj = {} にします。 アラート( obj ); // "[オブジェクト オブジェクト]" ?
文字列"[object Object]"
を生成するコードはどこにありますか?これは組み込みのtoString
メソッドですが、どこにあるのでしょうか? obj
が空です!
…しかし、短い表記法obj = {}
obj = new Object()
と同じです。ここで、 Object
組み込みのオブジェクト コンストラクター関数であり、独自のprototype
がtoString
やその他のメソッドで巨大なオブジェクトを参照します。
何が起こっているかは次のとおりです。
new Object()
が呼び出される (またはリテラル オブジェクト{...}
が作成される) と、その[[Prototype]]
は、前の章で説明したルールに従ってObject.prototype
に設定されます。
したがって、 obj.toString()
が呼び出されるとき、メソッドはObject.prototype
から取得されます。
次のようにして確認できます。
obj = {} にします。 アラート(obj.__proto__ === Object.prototype); // 真実 アラート(obj.toString === obj.__proto__.toString); //真実 アラート(obj.toString === Object.prototype.toString); //真実
Object.prototype
の上のチェーンには[[Prototype]]
が存在しないことに注意してください。
アラート(Object.prototype.__proto__); // null
Array
、 Date
、 Function
などの他の組み込みオブジェクトも、メソッドをプロトタイプに保持します。
たとえば、配列[1, 2, 3]
を作成する場合、デフォルトのnew Array()
コンストラクターが内部的に使用されます。したがって、 Array.prototype
そのプロトタイプとなり、メソッドを提供します。これは非常にメモリ効率が良いです。
仕様により、すべての組み込みプロトタイプの先頭にはObject.prototype
があります。 「すべてはオブジェクトから継承される」と言う人がいるのはこのためです。
全体像は次のとおりです (3 つの組み込みが収まる場合)。
プロトタイプを手動でチェックしてみましょう。
arr = [1, 2, 3] とします。 // Array.prototype を継承しますか? alert( arr.__proto__ === Array.prototype ); // 真実 // では、Object.prototype から? alert( arr.__proto__.__proto__ === Object.prototype ); // 真実 // 先頭には null が入ります。 alert( arr.__proto__.__proto__.__proto__ ); // null
プロトタイプ内の一部のメソッドは重複する場合があります。たとえば、 Array.prototype
には、カンマ区切りの要素をリストする独自のtoString
があります。
arr = [1, 2, 3] とします アラート(arr); // 1,2,3 <-- Array.prototype.toString の結果
前に見たように、 Object.prototype
にもtoString
がありますが、 Array.prototype
チェーンの近くにあるため、配列バリアントが使用されます。
Chrome 開発者コンソールなどのブラウザ内ツールでも継承が表示されます (組み込みオブジェクトにはconsole.dir
使用する必要がある場合があります)。
他の組み込みオブジェクトも同様に機能します。関数であっても、それらは組み込みFunction
コンストラクターのオブジェクトであり、そのメソッド ( call
/ apply
など) はFunction.prototype
から取得されます。関数にも独自のtoString
があります。
関数 f() {} alert(f.__proto__ == Function.prototype); // 真実 alert(f.__proto__.__proto__ == Object.prototype); // true、オブジェクトから継承
最も複雑な処理は、文字列、数値、ブール値で発生します。
私たちが覚えているように、それらは物体ではありません。ただし、それらのプロパティにアクセスしようとすると、組み込みコンストラクターString
、 Number
、およびBoolean
使用して一時ラッパー オブジェクトが作成されます。彼らはメソッドを提供しては消えていきます。
これらのオブジェクトは私たちには見えないように作成され、ほとんどのエンジンはそれらを最適化しますが、仕様ではまさにこのように説明されています。これらのオブジェクトのメソッドはプロトタイプにも存在し、 String.prototype
、 Number.prototype
、およびBoolean.prototype
として利用できます。
値null
およびundefined
はオブジェクト ラッパーがありません
特別な値null
とundefined
区別されます。これらにはオブジェクト ラッパーがないため、メソッドやプロパティは使用できません。また、対応するプロトタイプもありません。
ネイティブ プロトタイプは変更できます。たとえば、メソッドをString.prototype
に追加すると、そのメソッドはすべての文字列で使用できるようになります。
String.prototype.show = function() { アラート(これ); }; 「ドーン!」.show(); // ドーン!
開発の過程で、欲しい新しい組み込みメソッドのアイデアがあり、それをネイティブ プロトタイプに追加したくなるかもしれません。しかし、それは一般的に悪い考えです。
重要:
プロトタイプはグローバルであるため、競合が発生しやすくなります。 2 つのライブラリがメソッドString.prototype.show
を追加すると、一方のライブラリが他方のメソッドを上書きします。
したがって、一般に、ネイティブ プロトタイプを変更することは悪い考えであると考えられています。
最新のプログラミングでは、ネイティブ プロトタイプの変更が承認されるケースは 1 つだけです。それがポリフィルです。
ポリフィルは、JavaScript 仕様に存在するメソッドの代替を作成するための用語ですが、特定の JavaScript エンジンではまだサポートされていません。
その後、それを手動で実装し、組み込みプロトタイプにそれを設定することができます。
例えば:
if (!String.prototype.repeat) { // そのようなメソッドがない場合 // プロトタイプに追加します String.prototype.repeat = function(n) { // 文字列を n 回繰り返します // 実際にはコードはこれよりもう少し複雑になるはずです // (完全なアルゴリズムは仕様に記載されています) // しかし、不完全なポリフィルでも十分であるとみなされることがよくあります 新しい Array(n + 1).join(this) を返します。 }; } alert( "ラ".repeat(3) ); // ラララ
「デコレータと転送、呼び出し/適用」の章では、メソッドの借用について説明しました。
それは、あるオブジェクトからメソッドを取得し、それを別のオブジェクトにコピーするときです。
ネイティブ プロトタイプのメソッドの一部は借用されることがよくあります。
たとえば、配列のようなオブジェクトを作成している場合、いくつかのArray
メソッドをそれにコピーしたい場合があります。
例えば
obj = { にします 0: 「こんにちは」、 1:「世界!」、 長さ: 2、 }; obj.join = Array.prototype.join; アラート( obj.join(',') ); // こんにちは世界!
これが機能するのは、組み込みjoin
メソッドの内部アルゴリズムが正しいインデックスとlength
プロパティのみを考慮するためです。オブジェクトが実際に配列であるかどうかはチェックされません。多くの組み込みメソッドはそのようなものです。
もう 1 つの方法は、 obj.__proto__
Array.prototype
に設定して継承することです。これにより、すべてのArray
メソッドがobj
で自動的に使用可能になります。
ただし、 obj
すでに別のオブジェクトから継承している場合、それは不可能です。一度に継承できるオブジェクトは 1 つだけであることに注意してください。
借用方法は柔軟であり、必要に応じてさまざまなオブジェクトの機能を組み合わせることができます。
すべての組み込みオブジェクトは同じパターンに従います。
メソッドはプロトタイプ ( Array.prototype
、 Object.prototype
、 Date.prototype
など) に保存されます。
オブジェクト自体にはデータ (配列項目、オブジェクトのプロパティ、日付) のみが保存されます。
プリミティブは、ラッパー オブジェクトのプロトタイプ ( Number.prototype
、 String.prototype
、およびBoolean.prototype
にもメソッドを格納します。ラッパーオブジェクトを持たないのはundefined
とnull
です
組み込みプロトタイプを変更したり、新しいメソッドを追加したりできます。ただし、それらを変更することはお勧めできません。唯一許容されるケースは、おそらく新しい標準を追加する場合ですが、それが JavaScript エンジンでまだサポートされていない場合です。
重要度: 5
すべての関数のプロトタイプに、 ms
ミリ秒後に関数を実行するメソッドdefer(ms)
を追加します。
これを実行すると、次のようなコードが機能するはずです。
関数 f() { アラート("こんにちは!"); } f.defer(1000); // 「こんにちは!」を表示します。 1秒後
Function.prototype.defer = function(ms) { setTimeout(this, ミリ秒); }; 関数 f() { アラート("こんにちは!"); } f.defer(1000); // 「こんにちは!」を表示します。 1秒後
重要度: 4
すべての関数のプロトタイプにメソッドdefer(ms)
を追加します。このメソッドはラッパーを返し、呼び出しをms
ミリ秒遅らせます。
これがどのように機能するかの例を次に示します。
関数 f(a, b) { アラート( a + b ); } f.defer(1000)(1, 2); // 1秒後に3を表示
引数は元の関数に渡す必要があることに注意してください。
Function.prototype.defer = function(ms) { f = これとします。 return function(...args) { setTimeout(() => f.apply(this, args), ms); } }; // チェックしてください 関数 f(a, b) { アラート( a + b ); } f.defer(1000)(1, 2); // 1秒後に3を表示
注意: this
f.apply
で使用して、オブジェクト メソッドに対して装飾が機能するようにします。
したがって、ラッパー関数がオブジェクト メソッドとして呼び出された場合、 this
元のメソッドf
に渡されます。
Function.prototype.defer = function(ms) { f = これとします。 return function(...args) { setTimeout(() => f.apply(this, args), ms); } }; ユーザー = { にします 名前:「ジョン」、 SayHi() { アラート(この名前); } } user.sayHi = user.sayHi.defer(1000); user.sayHi();