プロトタイプ継承の実装で祖先メソッドを実装する方法

継承を操作するとき、新しいクラスがそれをオーバーライドするため、先祖メソッド(親クラスのメソッド)へのアクセス関数がコンストラクター(Javascriptのクラスのアナログ)または子孫メソッドにあることがあります。 関数(メソッド)だけでなく、完全に理解可能な表記法により、名前が語り、指定された祖先(「great-great-great-」ではなく「great-3 times」)にアクセスできるようにします。



プロトタイプの継承メソッドを基礎としてみましょう。これは、継承されたクラスのチェーンを記述するときに最小限のアクションを実行すると同時に、可能な限り基本的な操作と継承プロパティをサポートするという点で最も効果的です。 祖先にアクセスするために、彼は.superclassプロパティを作成します。 (彼らはこれがクラスへの基本的なアプローチとして採用されたとCrockfordによって一般化されたと主張していますが、リンクは見つかりませんでしたが、その方法が考えられ経済的であることが重要です。著者は不明です。)



まず、このメソッドのコードと簡単なコメントを示します。

function inherit2(Inherited, Parent){ //  var F = function(){}; F.prototype = Parent.prototype; (Inherited.prototype = new F()).constructor = Inherited; //    //   ,       Inherited.superclass = Parent.prototype; //  }
      
      





(もちろん、以前はJavascriptの継承のトピックに興味を持っていた人は、彼を認識していました。) javascript.ru/tutorial/object/inheritanceで詳細に調べられ、先祖のメソッドへのアクセスの問題もそこで取り上げられました。 .constructor "それ自体へのリンクを閉じて、クラスの将来のインスタンスが正しい.constructorプロパティを持つようにします。 ただし、当然ながら、継承されたクラス間の関係の正確性を失い(存在せず、言語に含まれていませんでした)、親クラスにアクセスするための "ボーナス" .superclassプロパティを作成する必要がありました。



先祖の方法が必要なのはなぜですか?



継承とは、子孫の名前が先祖の名前と一致する場合、プロパティを「上書き」(シールド)することを意味します。 関数を記述し、たとえば後継クラスのメソッドの機能を拡張した場合、単純化された(または以前の、または一般的な)機能の一部を書き換える必要があります。必要に応じて繰り返します。 これはすでに混乱です。 さて、メソッドを完全に変更する必要がありましたが、古いメソッドを繰り返してから少し追加する必要がある場合があります。 (したがって、プロパティよりもメソッドの方が先祖へのアクセスが重要です。プロパティは常に完全に書き換えられます。)ここで、「ボーナス」プロパティ「 .superclass 」が役立ち 、親のプロトタイプを示します。



スキームA => B => C => c01.MethodXに従って継承されたクラスの場合



  function A(){this.xa =1;} function B(){this.xb =1;} function C(){this.xc =1;} A.prototype.MethodX ='protoA'; inherit2(B, A); B.prototype.MethodX ='protoB'; B.prototype.xx ='protoX'; inherit2(C, B); C.prototype.MethodX = 'protoC'; var c01 = new C();
      
      





祖先メソッドにアクセスするこのような方法を取得します。



 Alert = console.log; Alert(c01.MethodX); //'protoC' //  Alert(c01.constructor.prototype.MethodX); //'protoC' //    Alert(c01.constructor.prototype.superclass.MethodX); //'proto' //   Alert(c01.constructor.prototype.superclass.constructor.superclass.MethodX); //'protoA'.
      
      





(これがどのように機能するかを見るには、 inherit2()の実行可能例を参照してください Firebugで見ても害はありません)。)

このような呼び出しは使用するには不便ですが、同僚やフォロワーを混同するのは便利です。最初に.constructor.prototype使用し 、次に.superclassを使用して、必要なことを言うのではなく、 先祖へのアピールの深さの数を示すことはできません。 全体として、フォームの小さな機能が必要です:



 object.ancestor(name, level) - //  /   ,
      
      





これは最も深いクラスに配置され、「上書きされた」メソッドを使用して新しいメソッドの機能を拡張できます(そして、誰も隣のメソッドに煩わされることはありません)。



メソッドのコンテキスト( this )が基本クラス(AおよびB)がまだ知らない最後のクラスに属する「強化」されるという事実には小さな問題が残りますが、これは現実のプロパティであり、さらに-完全に書き直すことを望まない祖先メソッド。 昔ながらの方法で書き直すことができますが、原始的な方法で未知のより複雑な環境を理解することにより、事前に注意を払うことができます。



また、「スーパークラス」の概念(Javaがどこから来たのかは明らかですが、コンストラクターは通常、プロトタイプではなくクラスと呼ばれるため混乱します)を「祖先クラス」 _anc (祖先)に置き換えます。 これは、コンストラクターが相続人に対して相対的なものである必要があるため、最終関数のロジックは単純化されます。



受け取りたいもの

c01 ['MethodX'] c01.ancestor( 'MethodX'、0)
c01.constructor.prototype ['MethodX'] c01.ancestor( 'MethodX')
c01.constructor.prototype._anc.prototype ['MethodX'] c01.ancestor( 'MethodX'、2)
c01.constructor.prototype._anc.prototype._anc.prototype ['MethodX'] c01.ancestor( 'MethodX'、3)
...
同時に、 c01.constructorc01._ancになる可能性があります。これは、各newでこれを記述した場合、つまり newの代わりに特別な関数を作成します(このような関数create()を呼び出す傾向があります)が、オブジェクトの作成の指標としてnewを保存するために(今のところ)これを行いません。



結果はそのような機能によって達成されます:



 function ancestor(name, level, constr){ level = level || (level ==0 ? 0 : 1); var t =this; return level <= 1 ? (level ? constr && constr.prototype[name] || t.constructor.prototype[name] : t[name]) : arguments.callee.call(this , name , level -1 , constr && (constr.prototype._anc || constr.prototype.constructor) || t._anc // ( )    ); }
      
      





inherit3()のソースコードも._anc()関数を使用するように少し変更されました。

 function inherit3(Inherited, Parent){ var F = function(){}; F.prototype = Parent.prototype; (Inherited.prototype = new F())._anc = Parent; Inherited.prototype.constructor = Inherited; }
      
      





先祖へのアクセスのロジック。



メソッドの現在の定義ではなく、以前の継承レベルの定義に目を向けます。



メソッドの正確な定義を取得できない場合、プロトタイプチェーンは、最後の世代より前ステップレベルで取得された最新の親メソッドを取得します。



プロトタイプがない場合、欠落しているプロパティのエラーが発生してはならないときに、関数の「親切の爆弾」(障害)に到達しますが、チェーンには常に最初のメソッドが定義されています。 プロパティがまったくない場合、どのレベルでも未定義になります。



親切な爆弾を持ちたくない場合は、プロパティがconstr.prototype._ancの代わりにないときに、いくつかの偽のプロパティを作成します。たとえば、( constr.prototype._anc || constr.fail.fail )または単に( constr.prototype._anc || fail ) 。



  function A(){this.prop =1;} function B(){this.prop =1;} function C(){this.prop =1;} A.prototype.protoProp ='protoA'; inherit3(B, A); B.prototype.protoProp ='protoB'; inherit3(C, B); C.prototype.protoProp ='protoC'; var c01 = new C(); A.prototype.ancestor = ancestor; Alert(c01['protoProp'], c01.ancestor('protoProp', 0)) //'protoC protoC' Alert(c01.constructor.prototype.protoProp, c01.ancestor('protoProp')) //'protoC protoC' Alert(c01.ancestor('protoProp', 2) ); //'protoB' Alert(c01.ancestor('protoProp', 3) ); //'protoA' Alert(c01.ancestor('protoProp', 4) ); //'protoA' Alert(c01.ancestor('protoProp', 5) ); //'protoA' Alert(c01.ancestor('protoProp', 6) ); //'protoA' Alert(c01.ancestor('protoProp2', 4) ); //'undefined'
      
      





スクリプトを見て、Firebugをひねりましょう- デモ (親切な爆弾付き)です。

子孫のプロトタイプは、継承の後に記述する必要があります。他の場所ではなく、 inherit2()でプロトタイプを操作した結果です。 共通の祖先のプロトタイプは、すべての継承の後に祖先()メソッドを割り当てることによって示されるように、どこでも変更できます。 例では、プロパティはメソッドではなくどこでも使用されました-簡潔にするために、フォーマットでメソッドを指定または実行しても問題はありません

 c01.ancestor('protoMethod', 3) //  c01.ancestor('protoMethod', 3)(args) // 
      
      





何を改善する必要がありますか?



記事のタスクはすべて完了しました。スリープ状態にできますか? いいえ、開発が止まることはなく、目の肥えた読者は、2つの異なる機能と多数の使用条件を受け取った後、道徳的な満足感を受け取りませんでした。 この方法で混乱するのは、祖先から子孫へのプロトタイプの変位です。 実際、多くの場合、オブジェクトにはすでにプロトタイプがあり、書き換える必要はなく、拡張(拡張)する必要があります。そうしないと、継承の3階で問題が発生し、メソッドを変更する必要があります。



読者のために完成したコードをすぐに準備しなかったのに、なぜ彼らに半完成品に行かせるのですか? このコードは開発者を考えるために必要なので、アクセス方法がどのように機能するかを知る必要があります。 スケルトンは最初の部分に組み込まれ、美しさや他の肉はありません。 スケルトンの構造は、その上に肉がない場合によく見えます。 望ましい改善を続けています。



例では、プロトタイプを書き直さないで(間違っている)、新しいプロパティを追加していることがわかります。 extend()関数の使用は、継承後にプロトタイプを拡張するように頼みます。 そのため、「手元に」拡張機能があると便利です。 jQueryおよび他の多くのライブラリにあります。または、速度を上げるために、関数を定義してから、継承メソッドに含めることができます。 基礎として、インターネット掘り出されたこの形式を取ることができます

 extend = function(obj, extObj){ //    if(arguments.length >2) for(var a =1, aL = arguments.length; a < aL; a++) arguments.callee(obj, arguments[a]) else{ for(var i in extObj) obj[i] = extObj[i]; return obj; };
      
      



「ケース間」では、いくつかの引数を使用して最初の引数を展開できるため、これは良いことです。 それまでの間、この関数でも " C.prototype.protoProp = 'protoC'; "ではなく、



 extend(C.prototype, {protoProp: 'protoC'}); //    .
      
      





.superclassはもう必要ないことに注意してください。 コードからスローされます。



さらに、この継承メソッドは非常に過酷で経済的であるため、 コンストラクターを実行しないため 、オブジェクトc01newが実行されるコンストラクターのプロパティのみを表示できます。 クラス間にコンストラクターランタイムはありません。 そのような過酷さが常に必要なわけではありません(ああ、いつもではありません)。 this.propertyによって生成されるコンストラクターのプロパティが存在し、プロトタイプの継承に参加することもあります(継承者のプロトタイプのプロパティになります)。



このアプローチをより便利なものに再作成する方法について-次のシリーズで。 これにはあらゆる使用理由がありますが、それはすべて継承メカニズムの要件の数と他の開発者をコードと混同したいという願望に依存しますこのため、最初のオプションが望ましいです。



開発者が好むクラスとコレクション(ハッシュ)の類似メソッドは何ですか?



もちろん、クラスと継承の実装で同様の問題が発生しました。これは、「親クラスのメソッドを呼び出す」という言葉で見つけることができます。

habrahabr.ru/blogs/javascript/40909



スーパークラスメソッドと同じタスクを実行する基本的な__parentメソッドを作成しました。 その中でも、先祖を参照する場合は、複数の呼び出しを使用する必要があります。

ライブラリには、さらにいくつかのメソッドがあります。

Mixin(A、B) -プロトタイプBからプロトタイプAへの追加、

クローン() -オブジェクトのクローン作成、

拡張() -ハッシュ拡張; いくつかの引数についてのみ。



スーパークラスはそのようなメソッドに接続しません:["hasOwnProperty"、 "isPrototypeOf"、 "propertyIsEnumerable"、 "toLocaleString"、 "toString"、 "valueOf"]は良い考えです。



祖先へのアクセスの別の例(Rezigから)


ejohn.org/blog/simple-javascript-inheritance(John Resigによる単純なJavaScript継承)では、クラスの類似性を強調する特別なClassコンストラクターとともに_superおよびextendメソッドの存在を確認しています。 ここで、 extendはハッシュ拡張ではなく、「上書きされた」プロパティの_superプロパティを設定するためにプロパティのリストをスクロールする特別な手順です。メソッドまたはプロパティは、親で書き換え可能なメソッドが見つかった場合に作成されます。 明らかに、これはより高価なメカニズムです。 祖先メソッドへの1回限りのアクセスを作成し、呼び出されたときにのみアクセスするのではなく、継承者が構築されるたびに「回避策」メソッドを作成し、祖先メソッドのリスト全体を実行します。 多くのリソースがある場合は正当化される可能性があります。新しいクラスを作成するよりも先祖に目を向けます。 一般に、先祖を参照することは関数の説明にあると想定されています。これは効果的な完全実行時メソッドであるため、直接リンクを記述するアプローチが正当化される場合があります。 別の方法として、リンク(オブジェクト)を介してアクセスすることもできます。.extendを使用してメソッドのリストを実行するコストはかかりませ



次のシリーズの内容



四頭反応のヒドラだけを埋めることにしたヒーローはほとんどヘラクレスですが、プログラマーがフレームを描くことを知っています。 彼は、検出された4つの問題(1-コードがメソッドに変換される、2-継承者のプロトタイプの拡張が追加される、3-混合メソッドの独立した使用が可能である、4-継承中にコードを記述するためのフォーマットが改善される)および1つのコード-1つのコード-それらを解決することに成功しました。 5番目の問題は同時に解決されなかったことが判明しましたが、書かれたコードの非オーバーロードと呼びましょう-将来の短い方法は相続人のプロトタイプだけでなく、他のハッシュを拡張することができます。 コードは、この記事で提供したソースコードを簡単に推測します。これは、開発者がアイデアの開発を追跡し、適切な実装を選択するのに役立ちます。 視聴者は、これがどのように起こったかを知りたがるでしょう-フレームをフレームごとに表示して例を見ると、これに役立ちます。 コードは本当に便利になります。 新聞のページでヘラクレスの冒険をフォローしてください。



All Articles