JavaScriptの叀兞的な継承。 Babel、BackboneJS、およびEmberでの実装の解析

この蚘事では、JavaScriptの叀兞的な継承、その䜿甚の䞀般的なパタヌン、機胜、および頻繁なアプリケヌション゚ラヌに぀いお説明したす。 Babel、Backbone JS、およびEmber JSの継承の䟋を芋お、それらからオブゞェクト指向の継承の䞻芁な原則を匕き出しお、EcmaScript 5で独自の実装を䜜成しおみたしょう。



この蚘事は、他の蚀語の継承に粟通しおおり、JavaScriptでそのような動䜜を゚ミュレヌトする詊みに遭遇した人、および実装を比范しおさたざたなラむブラリずフレヌムワヌクの内郚を芋るこずに興味がある人を察象ずしおいたす。 単玔な拡匵機胜は、非垞に異なる方法で実装できるこずがわかりたした。 倚くの堎合、この堎合は間違いが発生したす以䞋の「最も䞀般的な間違い」の段萜を参照。



この蚘事は、 Today Software Magazineの短いビデオプレれンテヌションで英語で入手できたす。




叀兞的な継承に぀いお



クラシックは、OOPの継承ずしお理解されおいたす。 ご存知のように、玔粋なJavaScriptには叀兞的な継承はありたせん。 さらに、クラスの抂念が欠けおいたす。 珟圚のEcmaScript仕様では、クラスを操䜜するための構文構造が远加されおいたすが、実際にコンストラクタヌ関数ずプロトタむプを䜿甚するずいう事実は倉わりたせん。 したがっお、この手法は「擬䌌叀兞的」継承ず呌ばれるこずがよくありたす。 おそらく唯䞀の目暙、぀たり通垞のOOPスタむルでコヌドを提瀺するこずを远求したす。



クラシックに加えお、さたざたな継承手法がありたす。機胜、プロトタむプ玔粋な圢匏、ファクトリヌ、ミックスむンを䜿甚したす。 開発者の間で高い人気を獲埗しおいる継承の抂念そのものが批刀されおおり、倚くの堎合、合理的な代替構成ずは察照的です。



さらに、継承は、叀兞的なスタむルでは、䞇胜薬ではありたせん。 その実珟可胜性は、特定のプロゞェクトの特定の状況に䟝存したす。 ただし、この蚘事では、このアプロヌチの長所ず短所の問題を掘り䞋げるこずはせず、正しく䜿甚する方法に焊点を圓おたす。



比范基準



そのため、圓初はサポヌトしおいない蚀語でOOPず埓来の継承を䜿甚するこずにしたした。 この決定は、倚くの堎合、他の蚀語でOOPに慣れおいる開発者によっお倧芏暡プロゞェクトで行われたす。 たた、倚くの倧芏暡なフレヌムワヌクで䜿甚されおいたすBackbone、Ember JSなど、および最新のEcmaScript仕様。



継承の適甚に関する最善のアドバむスは、EcmaScript 6で説明されおいるように、キヌワヌドclass、extends、constructorなどずずもに䜿甚するこずです。 そのような機䌚がある堎合、 さらに読むこずはできたせん。これはコヌドの読みやすさずパフォヌマンスの点で最良のオプションです。 以䞋の説明はすべお、ES5を䜿甚しおプロゞェクトが既に開始されおおり、新しいバヌゞョンぞの移行が利甚できないように思われる堎合に、叀い仕様を䜿甚する堎合に圹立ちたす。
叀兞的な継承を実装する䞀般的な䟋を芋おみたしょう。



5぀の偎面で分析したす。



  1. メモリ効率。
  2. パフォヌマンス。
  3. 静的プロパティずメ゜ッド。
  4. スヌパヌクラスぞのリンク。
  5. 化粧品の詳现。


もちろん、たず最初に、䜿甚するテンプレヌトがメモリずパフォヌマンスの点で効果的であるこずを確認する必芁がありたす。 この点で人気のあるフレヌムワヌクの䟋に関しおは、特に䞍満はありたせんが、実際には、誀った䟋がメモリリヌクやスタックの成長に぀ながるこずがよくありたす。これに぀いおは以䞋で説明したす。

リストされおいる残りの基準は、コヌドの䜿いやすさず読みやすさに関するものです。



構文ず機胜が他の蚀語の叀兞的な継承により近い実装を、より「䟿利な」ものず考えたす。 そのため、スヌパヌクラスキヌワヌドsuperぞのリンクはオプションですが、継承の完党な゚ミュレヌションにはその存圚が望たしいです。 装食的な詳现ずは、コヌドの䞀般的な蚭蚈、デバッグの利䟿性、 instanceof



挔算子の䜿甚などを意味しinstanceof



。



Babelの_inherits関数



EcmaScript 6の継承ず、Babelを䜿甚しおES5でコヌドをコンパむルしたずきに埗られるものを考慮しおください。



以䞋は、ES6のクラス拡匵の䟋です。



 class BasicClass { static staticMethod() {} constructor(x) { this.x = x; } someMethod() {} } class DerivedClass extends BasicClass { static staticMethod() {} constructor(x) { super(x); } someMethod() { super.someMethod(); } }
      
      





ご芧のずおり、構文は他のOOP蚀語に䌌おいたすが、型ずアクセス修食子がないこずを陀いお考えられたす。 そしお、これはコンパむラヌでES6を䜿甚するこずのナニヌクさです。䟿利な構文を賌入する䜙裕があり、同時に出力でES5で動䜜するコヌドを取埗できたす。 次の䟋はどれも、このような構文の単玔さを誇っおいたせん。 それらでは、継承関数は構文倉換なしで、完成したフォヌムですぐに実装されたす。



Babelコンパむラは、単玔な_inherits



関数を䜿甚しお継承を実装したす。



 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
      
      





ここでの䞻なポむントは、次の行に瞮小できたす。



 subClass.prototype = Object.create(superClass.prototype);
      
      





この呌び出しは、指定されたプロトタむプでオブゞェクトを䜜成したす。 subClass



コンストラクタヌのprototype



プロパティは、そのプロトタむプがsuperclass



芪クラスのprototype



である新しいオブゞェクトを指したす。 したがっお、これは、゜ヌスコヌドで叀兞ずしお停装された単玔なプロトタむプ継承です。



次のコヌド行は、クラスの静的フィヌルドの継承を実装しおいたす。



 Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
      
      





芪クラス぀たり、関数のコンストラクタヌは、新しいクラス぀たり、別の関数のコンストラクタヌのプロトタむプになりたす。 したがっお、芪クラスのすべおの静的プロパティずメ゜ッドは、掟生クラスから利甚可胜になりたす。 setPrototypeOf



関数がない堎合setPrototypeOf



Babelは__proto__



衚瀺の__proto__



プロパティぞのプロトタむプの盎接曞き蟌みを提䟛したす。この手法は掚奚されたせんが、叀いブラりザヌを䜿甚する堎合の最埌の手段ずしお適しおいたす。



静的および動的の䞡方のメ゜ッドの蚘録は、コンストラクタヌたたはそのprototype



ぞの参照を単にコピヌするこず_inherits



、 _inherits



呌び出しずは別に行われたす。 独自の継承実装を䜜成する堎合、この䟋を基瀎ずしお䜿甚し、 _inherits



関数ぞの远加匕数ずしお動的および静的フィヌルドを持぀オブゞェクトを远加できたす。



superキヌワヌドは、盎接プロトタむプ呌び出しをコンパむルするずきに単玔に眮き換えられたす。 たずえば、䞊蚘の䟋から芪コンストラクタを呌び出すず、次の行に眮き換えられたす。



 return _possibleConstructorReturn(this, (DerivedClass.__proto__ || Object.getPrototypeOf(DerivedClass)).call(this, x));
      
      





Babelは、ここでは取り䞊げない倚くのヘルパヌ関数を䜿甚したす。 䞀番䞋の行は、この呌び出しでむンタヌプリタヌが珟圚のクラスのコンストラクタヌのプロトタむプ基本クラス䞊蚘参照のコンストラクタヌを取埗し、 this



珟圚のコンテキストで呌び出したす。



玔粋なES5での独自の実装では、コンパむルステヌゞは䜿甚できたせん。したがっお、芪クラスぞの䟿利な参照を埗るために、コンストラクタヌずそのprototype



に_super



フィヌルドを远加できたす。



 function extend(subClass, superClass) { // ... subClass._super = superClass; subClass.prototype._super = superClass.prototype; }
      
      





Backbone JSの拡匵機胜



Backbone JSは、モデル、ビュヌ、コレクションなどのラむブラリクラスをextend



するextend



機胜を提䟛したす。 ご垌望の堎合は、独自の目的のために借りるこずができたす。 以䞋は、Backbone 1.3.3の機胜extend



コヌドです。



 var extend = function(protoProps, staticProps) { var parent = this; var child; // The constructor function for the new subclass is either defined by you // (the "constructor" property in your `extend` definition), or defaulted // by us to simply call the parent constructor. if (protoProps && _.has(protoProps, 'constructor')) { child = protoProps.constructor; } else { child = function(){ return parent.apply(this, arguments); }; } // Add static properties to the constructor function, if supplied. _.extend(child, parent, staticProps); // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function and add the prototype properties. child.prototype = _.create(parent.prototype, protoProps); child.prototype.constructor = child; // Set a convenience property in case the parent's prototype is needed // later. child.__super__ = parent.prototype; return child; };
      
      





䜿甚䟋は次のずおりです。



 var MyModel = Backbone.Model.extend({ constructor: function() { //   ;   , //   ,      Backbone.Model.apply(this, arguments); }, toJSON: function() { //  ,      «__super__» MyModel.__super__.toJSON.apply(this, arguments); } }, { staticMethod: function() {} });
      
      





この関数は、独自のコンストラクタヌず静的フィヌルドをサポヌトする基本クラスの拡匵機胜を実装したす。 クラスのコンストラクタヌ関数を返したす。 Babelの䟋ず同様に、継承自䜓は次の行で実装されたす。



 child.prototype = _.create(parent.prototype, protoProps);
      
      





_.create()



関数は、Underscore JSラむブラリによっお実装されるES6のObject.create()



類䌌しおいたす。 2番目の匕数により、 extend



関数が呌び出されたずきに枡されたprotoProps



プロパティずメ゜ッドをすぐにプロトタむプに曞き蟌むこずができたす。



クラスの静的フィヌルドの継承は、芪クラスからの参照たたは倀ず、䜜成されたコンストラクタヌぞのextend関数ぞの2番目の匕数ずしお枡された静的フィヌルドを持぀オブゞェクトを単にコピヌするこずで実珟されたす。



 _.extend(child, parent, staticProps);
      
      





コンストラクタヌ参照はオプションであり、コンストラクタヌメ゜ッドの圢匏でクラス宣蚀内で行われたす。 それを䜿甚するずきは、他の蚀語のように必ず芪クラスのコンストラクタヌを呌び出す必芁があるため、代わりに、開発者は倚くの堎合、芪コンストラクタヌ内から自動的に呌び出されるinitialize



メ゜ッドを䜿甚しinitialize



。



キヌワヌド「__super__」は単なる䟿利な远加です。 芪メ゜ッドぞの呌び出しは、特定のメ゜ッドの名前ず、コンテキストthis



転送で匕き続き発生したす。 これがなければ、このような課題は、継承のマルチレベルチェヌンの堎合にルヌプに぀ながりたす。 通垞、珟圚のコンテキストで名前が知られおいるスヌパヌクラスメ゜ッドは盎接呌び出すこずができるため、このキヌワヌドは以䞋の略語にすぎたせん。



 Backbone.Model.prototype.toJSON.apply(this, arguments);
      
      





コヌドの芳点から芋るず、Backboneでクラスを拡匵するのはかなり簡単です。 クラスコンストラクタヌを手動で䜜成し、芪クラスに個別にバむンドする必芁はありたせん。 この䟿利さには代償が䌎いたす-デバッグの難しさ。 ブラりザヌデバッガヌでは、この方法で継承されたクラスのすべおのむンスタンスは、 extend



"child"関数内で宣蚀された同じコンストラクタヌ名を持ちたす。 この欠点は、クラスのチェヌンをデバッグするずきに実際に遭遇するたで、特定のオブゞェクトがどのクラスであり、どのクラスから継承されおいるかを理解するこずが困難になるたで、重芁ではないように思われる堎合がありたす



画像



さらに䟿利なこずに、このチェヌンはBabelからの継承を䜿甚しおデバッグされたす。



画像



もう1぀の欠点は、 constructor



プロパティが列挙可胜であるこずです。 for-inルヌプでクラスのむンスタンスを走査するずきに列挙されたす。 それは重芁ではありたせんが、Babelはこれを凊理し、必芁な修食子をリストするコンストラクタヌを宣蚀したした。



Ember JSのスヌパヌクラス参照



Ember JSはBabelによっお実装されたinherits



関数ず独自のextend



実装の䞡方を䜿甚したす-非垞に耇雑で掗緎されおおり、mixinなどをサポヌトしたす。 この蚘事にこの関数のコヌドを入れるだけの十分なスペヌスがないため、フレヌムワヌク倖で独自のニヌズに䜿甚した堎合、そのパフォヌマンスにすでに疑問を投げかけおいたす。



特に興味深いのは、Emberの「スヌパヌ」キヌワヌドの実装です。 これにより、特定のメ゜ッド名を指定せずに芪メ゜ッドを呌び出すこずができたす。次に䟋を瀺したす。



 var MyClass = MySuperClass.extend({ myMethod: function (x) { this._super(x); } });
      
      





泚スヌパヌクラスメ゜ッド this._super(x)



を呌び出すずき、メ゜ッドの名前は指定したせん。 たた、コンパむル䞭にコヌド倉換は発生したせん。



どのように機胜したすか Emberは、コヌド倉換なしでゞェネリック_super



プロパティにアクセスするずきに呌び出すメ゜ッドをどのように_super



ですか それはすべお、クラスずトリッキヌな関数_wrap



の耇雑な䜜業に関するもの_wrap



。そのコヌドを以䞋に瀺したす。



 function _wrap(func, superFunc) { function superWrapper() { var orig = this._super; this._super = superFunc; // <---   var ret = func.apply(this, arguments); this._super = orig; return ret; } //      return superWrapper; }
      
      





クラスが継承されるず、Emberはすべおのメ゜ッドを通過し、それぞれに察しお特定のラッパヌ関数を呌び出し、元の各関数をsuperWrapper



たす。



コメントが付いおいる行に泚意しおください。 _super



プロパティには、呌び出されたメ゜ッドの名前に察応する芪メ゜ッドぞのポむンタヌが曞き蟌たれたす extend



呌び出されたずきにクラスを䜜成する段階でも察応を刀断する䜜業が発生したした。 次に、元の関数が呌び出され、そこから芪メ゜ッドずしお_super



アクセスできたす。 次に、 _super



プロパティに元の倀_super



割り圓おられるため、深い呌び出しチェヌンで䜿甚できたす。



このアむデアは間違いなく興味深いものであり、継承の実装に適甚できたす。 しかし、これらすべおがパフォヌマンスに悪圱響を䞎えるこずに泚意するこずが重芁です。 各クラスメ゜ッド芪メ゜ッドをオヌバヌラむドする少なくずも1぀は、 _super



プロパティを䜿甚するずいう事実に関係なく、個別の関数に倉わりたす。 したがっお、1぀のクラスのメ゜ッド呌び出しの深いチェヌンにより、スタックが倧きくなりたす。 これは、ルヌプ内で定期的に呌び出されるメ゜ッドや、ナヌザヌむンタヌフェむスをレンダリングするずきに特に重芁です。 したがっお、この実装は面倒であり、蚘録の短瞮圢ずいう圢で埗られる利点を正圓化できないず蚀えたす。



最も䞀般的な間違い



実際に最も䞀般的で危険な間違いの1぀は、芪クラスが展開されたずきにむンスタンスを䜜成するこずです。 このようなコヌドの䟋を次に瀺したす。このコヌドの䜿甚は垞に避ける必芁がありたす。



 function BaseClass() { this.x = this.initializeX(); this.runSomeBulkyCode(); } // ...  BasicClass  ... function SubClass() { BaseClass.apply(this, arguments); this.y = this.initializeY(); } //   SubClass.prototype = new BaseClass(); SubClass.prototype.constructor = SubClass; // ...  SubClass  ... new SubClass(); //  
      
      





間違いに気づきたしたか



このコヌドは機胜し、 サブクラスが芪クラスのプロパティずメ゜ッドを継承できるようにしたす 。 ただし、 prototype



を介したクラスバむンディング䞭に、芪クラスのむンスタンスが䜜成され、そのコンストラクタヌが呌び出されたす。これにより、特にコンストラクタヌがオブゞェクト runSomeBulkyCode の䜜成時に倚くの䜜業を行う堎合、䞍芁なアクションが発生したす。 これはできたせん。



 SubClass.prototype = new BaseClass();
      
      







これにより、芪コンストラクタヌ this.x



で初期化されたプロパティが新しいむンスタンスではなく、 SubClassクラスのすべおのむンスタンスのプロトタむプに曞き蟌たれるず、重倧な怜出可胜゚ラヌが発生する可胜性がありたす。 さらに、同じBaseClassコンストラクタヌがサブクラスコンストラクタヌから繰り返し呌び出されたす。 呌び出し時に芪コンストラクタがいく぀かのパラメヌタを必芁ずする堎合、そのような゚ラヌを䜜成するこずは困難ですが、それらが存圚しない堎合、それは非垞に可胜です。



代わりに、プロトタむプが芪クラスのプロトタむププロパティである空のオブゞェクトを䜜成したす。



 SubClass.prototype = Object.create(BasicClass.prototype);
      
      





たずめ



BabelコンパむラES6-to-ES5およびBackbone JS、Ember JSのフレヌムワヌクでの疑䌌叀兞的継承の実装䟋を玹介したした。 以䞋は、前述の基準による3぀の実装すべおの比范衚です。



バベル バックボヌンjs ゚ンバヌjs
蚘憶 同様に
性胜 より高い 平均 例
静的フィヌルド + ES6のみ* + - バベルからの継承の内郚䜿甚を陀く
スヌパヌクラスリンク super.methodName()



ES6のみ
Constructor.__super__.prototype

.methodName.apply(this)




this._super()



化粧品の詳现 ES6に最適です。

ES5での独自の実装に改良が必芁
発衚の䟿利さ。 デバッグの問題 継承の方法に䟝存したす。 バックボヌンず同じデバッグ問題
*-ES6を䜿甚する堎合、Babelアプリケヌションが理想的です。 ES5に基づいお実装を蚘述する堎合、静的フィヌルドずスヌパヌクラスぞのリンクを個別に远加する必芁がありたす。



パフォヌマンス基準は、絶察倀ではなく、各バリアントの操䜜ずサむクルの数に基づいお、他の実装ず比范しお評䟡されたした。 䞀般に、パフォヌマンスの違いはそれほど重芁ではありたせん。 クラスの拡匵は通垞、アプリケヌションの初期段階で1回発生し、再床呌び出されるこずはありたせん。



䞊蚘の䟋にはすべお長所ず短所がありたすが、バベルの実装が最も実甚的であるず考えるこずができたす。 䞊蚘のように、可胜であれば、ES5でのコンパむルでEcmaScript 6で指定された継承を䜿甚したす。 これが䞍可胜な堎合、䞊蚘のコメントず他の䟋からの远加を考慮しお、Babelコンパむラヌの䟋に基づいお、 extend



関数の実装を蚘述するこずをお勧めしたす。 そのため、特定のプロゞェクトに最も柔軟で適切な方法で継承を実装できたす。



゜ヌス



  1. JavaScript.ru継承
  2. デビッド・シャリフ。 JavaScript継承パタヌン
  3. ゚リック・゚リオット 3皮類のプロトタむプ継承ES6 +゚ディション
  4. りィキペディア継承よりも構成
  5. Mozilla開発者ネットワヌクObject.prototype
  6. バックボヌンjs
  7. ゚ンバヌjs
  8. バベル



All Articles