ホームページ > 記事 > ウェブフロントエンド > JavaScript プロトタイプの詳細な調査は、プロトタイプの継承_JavaScript スキルほど単純ではありません
JavaScript のオブジェクトのプロトタイプ属性は、オブジェクト タイプのプロトタイプへの参照を返すことができます。これを理解するには、まずオブジェクト型 (Type) とプロトタイプ (プロトタイプ) の概念を正しく理解する必要があります。
1 プロトタイプとは
JavaScript のオブジェクトのプロトタイプ属性は、オブジェクト型のプロトタイプへの参照を返すことができます。これを理解するには、まずオブジェクト型 (Type) とプロトタイプ (プロトタイプ) の概念を正しく理解する必要があります。
オブジェクトのクラス (Class) とオブジェクトのインスタンス (Instance) の間には「作成」関係があると前述しました。したがって、「クラス」をオブジェクトの特性のモデリングと見なし、オブジェクトをクラスと見なします。特性のこと、またはクラス (Class) はオブジェクトのタイプ (Type) です。たとえば、前の例では、p1 と p2 の型は両方とも Point です。JavaScript では、これは
p1 instanceof Point
p2 instanceof Point
によって確認できます。 p1 と p2 の唯一の型ではありません。p1 と p2 は両方ともオブジェクトであるため、Object は Point よりも一般的なクラスであるため、Object と Point の間には派生関係があると言えます。この関係は「継承」と呼ばれることが後でわかりますが、これはオブジェクト間の一般化された関係の特殊なケースでもあり、オブジェクト指向では不可欠な基本的な関係です。
オブジェクト指向の分野では、記述可能な抽象関係のペアはインスタンスと型だけではありません。JavaScript では、もう 1 つの重要な抽象関係が型 (Type) とプロトタイプ (プロトタイプ) です。この関係は、より高いレベルの抽象関係であり、タイプとインスタンスの間の抽象関係で 3 層のチェーンを形成します。
実生活では、何かが別のものに基づいているとよく言われます。これら 2 つは同じタイプである場合もあれば、異なるタイプである場合もあります。 「瓢箪を追って瓢箪を描く」という慣用句ですが、ここでの瓢箪はプロトタイプであり、それをJavaScriptのプロトタイプで表すと「ladle.prototype = ある瓢箪」または「ladle.prototype = 新しい瓢箪」になります。 ()」。
プロトタイプを深く理解するには、その設計パターンの 1 つであるプロトタイプ パターンを学習します。このパターンの核心は、プロトタイプ インスタンスを使用して、作成するオブジェクトのタイプを指定し、これらのプロトタイプをコピーして新しいオブジェクトを作成することです。 JavaScript プロトタイプはこのメソッドに似ています。
プロトタイプ パターンの詳細については、この記事の範囲を超えているため、「デザイン パターン」(「デザイン パターン」) を参照してください。
型とインスタンスの関係とは異なり、プロトタイプと型の関係では、型は一度に 1 つのプロトタイプのみを持つことができる必要があります (インスタンスは明らかに一度に複数の型を持つことができます)。 JavaScript の場合、この制限には 2 つの意味があります。1 つは、特定の JavaScript タイプごとにプロトタイプが 1 つだけあることです。デフォルトでは、このプロトタイプは Object オブジェクトです (オブジェクト タイプではないことに注意してください)。 2 つ目は、このオブジェクトが属する型がプロトタイプ関係を満たす型チェーンである必要があるということです。たとえば、p1 のタイプは Point と Object であり、Object オブジェクトは Point のプロトタイプです。タイプが ClassA、ClassB、ClassC、および Object であるオブジェクトがある場合、これら 4 つのクラスは完全なプロトタイプ チェーンを形成する必要があります。
興味深いのは、JavaScript では型のプロトタイプの型を指定しないことです (これも非常に厄介なステートメントです)。そのため、任意の型 (通常はある種のオブジェクト) にすることができます。したがって、オブジェクト-型-プロトタイプ (オブジェクト) ) リング構造やその他の興味深いトポロジ構造を形成する場合があります。これらの構造は JavaScript にさまざまな用途をもたらし、その中には賢いだけでなく美しさに満ちたものもあります。以下ではプロトタイプの使い方を中心に紹介します。
2 プロトタイプの使用上のヒント
プロトタイプの使用スキルを理解する前に、まずプロトタイプの特性を理解する必要があります。まず、JavaScript は各型 (Type) にプロトタイプ属性を提供します。この属性をオブジェクトに指定すると、このオブジェクトがこの型の「プロトタイプ」になります。これは、この型によって作成されたすべてのオブジェクトがこのプロトタイプの特性を持つことを意味します。 。さらに、JavaScript オブジェクトは動的であり、プロトタイプも例外ではありません。プロトタイプに属性を追加または削除すると、このタイプのプロトタイプが変更されます。たとえば、このプロトタイプによって作成されたすべてのオブジェクトに影響します。
<script> function Point(x,y) { this.x = x; this.y = y; } var p1 = new Point(1,2); var p2 = new Point(3,4); Point.prototype.z = 0; //动态为Point的原型添加了属性 alert(p1.z); alert(p2.z); //同时作用于Point类型创建的所有对象 </script>
a という名前の属性がオブジェクト型のプロトタイプに追加され、オブジェクト自体に a という同じ名前の属性がある場合、オブジェクトの属性 a にアクセスすると、オブジェクト自体のプロパティがプロトタイプ プロパティを「カバー」しますが、削除演算子を使用してオブジェクト自体のプロパティ a を削除しても、プロトタイプ プロパティは表示されなくなります。この機能を使用すると、オブジェクトのプロパティのデフォルト値を設定できます。例:
<script> function Point(x, y) { if(x) this.x = x; if(y) this.y = y; } Point.prototype.x = 0; Point.prototype.y = 0; var p1 = new Point; var p2 = new Point(1,2); </script>
上記の例では、プロトタイプを通じて Point オブジェクトのデフォルト値 (0,0) を設定します。 p1 の値は ( 0,0)、p2 の値は (1,2) で、delete p2.x、delete p2.y; によって p2 の値を (0,0) に復元できます。さらに興味深い例を次に示します。
<script> function classA() { this.a = 100; this.b = 200; this.c = 300; this.reset = function() { for(var each in this) { delete this[each]; } } } classA.prototype = new classA(); var a = new classA(); alert(a.a); a.a *= 2; a.b *= 2; a.c *= 2; alert(a.a); alert(a.b); alert(a.c); a.reset(); //调用reset方法将a的值恢复为默认值 alert(a.a); alert(a.b); alert(a.c); </script>
プロトタイプを使用すると、オブジェクトのプロパティに読み取り専用のゲッターを設定して、上書きされないようにすることもできます。以下に例を示します。
<script> function Point(x, y) { if(x) this.x = x; if(y) this.y = y; } Point.prototype.x = 0; Point.prototype.y = 0; function LineSegment(p1, p2) { //私有成员 var m_firstPoint = p1; var m_lastPoint = p2; var m_width = { valueOf : function(){return Math.abs(p1.x - p2.x)}, toString : function(){return Math.abs(p1.x - p2.x)} } var m_height = { valueOf : function(){return Math.abs(p1.y - p2.y)}, toString : function(){return Math.abs(p1.y - p2.y)} } //getter this.getFirstPoint = function() { return m_firstPoint; } this.getLastPoint = function() { return m_lastPoint; } this.length = { valueOf : function(){return Math.sqrt(m_width*m_width + m_height*m_height)}, toString : function(){return Math.sqrt(m_width*m_width + m_height*m_height)} } } var p1 = new Point; var p2 = new Point(2,3); var line1 = new LineSegment(p1, p2); var lp = line1.getFirstPoint(); lp.x = 100; //不小心改写了lp的值,破坏了lp的原始值而且不可恢复 alert(line1.getFirstPoint().x); alert(line1.length); //就连line1.lenght都发生了改变 </script>
this.getFirstPoint() を次のように書き換えます。
this.getFirstPoint = function()
{
function GETTER(){}; GETTER.prototype = m_firstPoint;
return new GETTER();
}
を使用すると、この問題を回避し、m_firstPoint 属性の読み取り専用の性質を確保できます。
<script> function Point(x, y) { if(x) this.x = x; if(y) this.y = y; } Point.prototype.x = 0; Point.prototype.y = 0; function LineSegment(p1, p2) { //私有成员 var m_firstPoint = p1; var m_lastPoint = p2; var m_width = { valueOf : function(){return Math.abs(p1.x - p2.x)}, toString : function(){return Math.abs(p1.x - p2.x)} } var m_height = { valueOf : function(){return Math.abs(p1.y - p2.y)}, toString : function(){return Math.abs(p1.y - p2.y)} } //getter this.getFirstPoint = function() { function GETTER(){}; GETTER.prototype = m_firstPoint; return new GETTER(); } this.getLastPoint = function() { function GETTER(){}; GETTER.prototype = m_lastPoint; return new GETTER(); } this.length = { valueOf : function(){return Math.sqrt(m_width*m_width + m_height*m_height)}, toString : function(){return Math.sqrt(m_width*m_width + m_height*m_height)} } } var p1 = new Point; var p2 = new Point(2,3); var line1 = new LineSegment(p1, p2); var lp = line1.getFirstPoint(); lp.x = 100; //不小心改写了lp的值,但是没有破坏原始的值 alert(line1.getFirstPoint().x); alert(line1.length); //line1.lenght不发生改变 </script>実際、オブジェクトを型のプロトタイプとして設定することは、この型をインスタンス化してオブジェクトの読み取り専用コピーを作成することと同じであり、コピーをいつ変更しても元のオブジェクトには影響しません。変更されたプロパティが同じ名前のコピー自身のプロパティによって上書きされない限り、元のオブジェクトへの変更はコピーに影響します。削除操作を使用して、同じ名前を持つオブジェクト自体のプロパティを削除すると、プロトタイプ プロパティの表示を復元できます。別の例を次に示します。
<script> function Polygon() { var m_points = []; m_points = Array.apply(m_points, arguments); function GETTER(){}; GETTER.prototype = m_points[0]; this.firstPoint = new GETTER(); this.length = { valueOf : function(){return m_points.length}, toString : function(){return m_points.length} } this.add = function(){ m_points.push.apply(m_points, arguments); } this.getPoint = function(idx) { return m_points[idx]; } this.setPoint = function(idx, point) { if(m_points[idx] == null) { m_points[idx] = point; } else { m_points[idx].x = point.x; m_points[idx].y = point.y; } } } var p = new Polygon({x:1, y:2},{x:2, y:4},{x:2, y:6}); alert(p.length); alert(p.firstPoint.x); alert(p.firstPoint.y); p.firstPoint.x = 100; //不小心写了它的值 alert(p.getPoint(0).x); //不会影响到实际的私有成员 delete p.firstPoint.x; //恢复 alert(p.firstPoint.x); p.setPoint(0, {x:3,y:4}); //通过setter改写了实际的私有成员 alert(p.firstPoint.x); //getter的值发生了改变 alert(p.getPoint(0).x); </script>上記の例は、プロトタイプを使用してオブジェクトの複数のコピーを迅速に作成できることを示しています。一般に、プロトタイプを使用して多数の複雑なオブジェクトを作成する方が、プロトタイプを使用するよりも優れています。他の方法では、オブジェクトをコピーする方がはるかに高速です。 1 つのオブジェクトをプロトタイプとして使用して、多数の新しいオブジェクトを作成することが、プロトタイプ パターンの本質であることに注意してください。
以下は例です:
<script> var p1 = new Point(1,2); var points = []; var PointPrototype = function(){}; PointPrototype.prototype = p1; for(var i = 0; i < 10000; i++) { points[i] = new PointPrototype(); //由于PointPrototype的构造函数是空函数,因此它的构造要比直接构造//p1副本快得多。 } </script>上記の使用テクニックに加えて、プロトタイプにはそのユニークな特性により他の用途があり、最も広く使用されており、おそらく最も一般的なものとして使用されます。最もよく知られている使用法は、継承をシミュレートすることです。これについては次のセクションで説明します。
3 プロトタイプの本質
プロトタイプの役割は上で述べました。次に、プロトタイプの本質をルールを通して明らかにしましょう。
プロトタイプは C の静的フィールドのように動作すると言います。プロトタイプのプロパティとしてプロパティを追加します。このプロパティは、この型によって作成されたすべてのインスタンスによって共有されますが、この共有は読み取り専用です。どのような場合でも、このプロパティは同じ名前の独自のプロパティで上書きすることのみが可能ですが、変更することはできません。つまり、オブジェクトが特定の属性を読み取るときは、必ず最初に自身のドメインの属性テーブルをチェックし、そのような属性が存在する場合はその属性を返します。それ以外の場合は、プロトタイプ ドメインを読み取り、その属性を返します。プロトタイプドメイン。さらに、JavaScript ではプロトタイプ フィールドで任意のタイプのオブジェクトを参照できるため、プロトタイプ フィールドを読み取った後に属性が見つからない場合、JavaScript はプロトタイプ フィールドがポイントするオブジェクトのプロトタイプ フィールドを再帰的に検索します。オブジェクトそのもの、またはサイクルが発生するまでは、次の図を使用してプロトタイプとオブジェクト インスタンスの関係を説明できます:
//TODO:
4 プロトタイプ
より 上記の分析から、オブジェクトをプロトタイプとして使用して多数のインスタンスを安全に作成できるプロトタイプを理解しました。これがプロトタイプの本当の意味とその価値です。プロトタイプのこの機能を使用してオブジェクトの継承をシミュレートできることは後で説明しますが、プロトタイプは継承のシミュレートに使用され、プロトタイプの重要な値でもありますが、決してそのコアではないことを知っておく必要があります。つまり、JavaScript したがって、プロトタイプのサポートは、そのオブジェクトの継承を実装するためだけに使用されるわけではありません。プロトタイプの継承がなくても、JavaScript のプロトタイプのメカニズムは依然として非常に便利です。
プロトタイプは型のコピーを構築するためのプロトタイプとしてオブジェクトのみを使用するため、大きな制限もあります。まず、型のプロトタイプ フィールドの値のコピーとして動作せず、参照コピーとして動作するため、「副作用」が生じます。プロトタイプの参照型プロパティのプロパティ値を変更すると (これもかなり厄介な説明です :P)、この型で作成されたすべてのインスタンスに完全に影響します。これはまさに必要なこと (特定のクラスのすべてのオブジェクトのデフォルト値の変更など) である場合もありますが、望ましくないこともあります (クラスの継承時など)。次に例を示します。
<script> function ClassA() { this.a=[]; } function ClassB() { this.b=function(){}; } ClassB.prototype=new ClassA(); var objB1=new ClassB(); var objB2=new ClassB(); objB1.a.push(1,2,3); alert(objB2.a); //所有b的实例中的a成员全都变了!!这并不是这个例子所希望看到的。 </script>