
実装要件:
- Node.jsネイティブ継承util.inheritsの使用
- 祖先クラスとクラスの子孫へのフィールドとメソッドの定義
- 子コンストラクターから親コンストラクターを呼び出す機能
- 子クラスのメソッドをオーバーライドする機能
- 子クラスでオーバーライドされたメソッドから親クラスのメソッドを呼び出す機能
基本的な例
継承によって接続された2つのクラスがあり、子のコンストラクターからClassName.super_.apply(this、arguments)を介して親クラスのコンストラクターを呼び出します。 当然、この呼び出しは、子の最初、コンストラクター、およびその終わりまたは中間の両方で行うことができます。 呼び出しは条件にラップできます。 祖先のコンストラクターの機能へのロールバックを完全に制御します。
var util = require('util'); // function ParentClass(par1, par2) { this.parentField1 = par1; this.parentField2 = par2; } function ChildClass(par1, par2) { ChildClass.super_.apply(this, arguments); this.childField1 = par1; this.childField2 = par2; } // util.inherits(ChildClass, ParentClass); // var obj = new ChildClass('Hello', 'World'); console.dir({ obj: obj }); /* : { obj: { parentField1: 'Hello', parentField2: 'World', childField1: 'Hello', childField2: 'World' } } */
拡張例
ここでは、プロトタイプを介して、親クラスと子の両方のメソッドとプロパティをすでに定義しています。 これらは、生成されたインスタンスのメソッドとプロパティではなく、クラス自体のメソッドとプロパティになることを思い出させてください。 それらは標本に表示されますが、プロトタイプに含まれています。 コンソールの結論は、すべてが正常に機能し、便利で予測可能なものであることを示しています。
var util = require('util'); // function ParentClass(par1, par2) { this.parentField1 = par1; this.parentField2 = par2; } // ParentClass.prototype.parentMethod = function(par) { console.log('parentMethod("' + par + '")'); }; // ParentClass.prototype.parentField = 'Parent field value'; // function ChildClass(par1, par2) { ChildClass.super_.apply(this, arguments); this.childField1 = par1; this.childField2 = par2; } // util.inherits(ChildClass, ParentClass); // ChildClass.prototype.childMethod = function(par) { console.log('childMethod("' + par + '")'); }; // ChildClass.prototype.childField = 'Child field value'; // var parentClassInstance = new ParentClass('Marcus', 'Aurelius'); var childClassInstance = new ChildClass('Yuriy', 'Gagarin'); // console.dir({ parentClassInstance: parentClassInstance, childClassInstance: childClassInstance }); console.dir({ objectFieldDefinedInParent: childClassInstance.parentField1, classFieldDefinedInParent: childClassInstance.parentField, objectFieldDefinedInChild: childClassInstance.childField1, classFieldDefinedInChild: childClassInstance.childField }); parentClassInstance.parentMethod('Cartesius'); childClassInstance.childMethod('von Leibniz'); /* : { parentClassInstance: { parentField1: 'Marcus', parentField2: 'Aurelius' }, childClassInstance: { parentField1: 'Yuriy', parentField2: 'Gagarin', childField1: 'Yuriy', childField2: 'Gagarin' } } { objectFieldDefinedInParent: 'Yuriy', classFieldDefinedInParent: 'Parent field value', objectFieldDefinedInChild: 'Yuriy', classFieldDefinedInChild: 'Child field value' } parentMethod("Cartesius") childMethod("von Leibniz") */
メソッドのオーバーライドの例
さらに興味深いことに、ParentClassにはmethodNameメソッドがあり、新しいオーバーライドされた実装から祖先メソッドを呼び出す機能を使用して、ChildClassの子孫で再定義する必要があります。
var util = require('util'); // function ParentClass(par1, par2) { this.parentField1 = par1; this.parentField2 = par2; } // ParentClass.prototype.methodName = function(par) { console.log('Parent method implementation: methodName("' + par + '")'); }; // function ChildClass(par1, par2) { ChildClass.super_.apply(this, arguments); this.childField1 = par1; this.childField2 = par2; } // util.inherits(ChildClass, ParentClass); // ChildClass.prototype.methodName = function(par) { // ChildClass.super_.prototype.methodName.apply(this, arguments); // console.log('Child method implementation: methodName("' + par + '")'); }; // var childClassInstance = new ChildClass('Lev', 'Nikolayevich'); // childClassInstance.methodName('Tolstoy'); /* : Parent method implementation: methodName("Tolstoy") Child method implementation: methodName("Tolstoy") */
親クラスのメソッドを呼び出すためのこの構築:ClassName.super_.prototype.methodName.apply(これ、引数)は確かに非常に面倒ですが、ネイティブノードの継承の実装に別の方法は見つかりませんでした。 私に起こった唯一の疑わしい改善は、次の例で与えられます。
継承の代替方法
先祖メソッドを呼び出す構文を簡素化するために、オーバーライドメソッドをFunction基本クラスに追加して価格を支払う必要があります。 一般的なすべての関数(ノードの現在のコンテキスト、またはサンドボックス/サンドボックス内、これがすべてシールドメモリコンテキストで実行されるコード-サンドボックス内で発生する場合) その後の呼び出しはエレガントになります:this.inherited(...)またはユニバーサルオプションthis.inherited.apply(this、arguments)を使用できます。このオプションでは、すべてのパラメーターを名前で親メソッドの呼び出しに置き換える必要はありません。
var util = require('util'); // Function.prototype.override = function(fn) { var superFunction = this; return function() { this.inherited = superFunction; return fn.apply(this, arguments); }; }; // function ParentClass(par1, par2) { this.parentField1 = par1; this.parentField2 = par2; } // ParentClass.prototype.methodName = function(par) { console.log('Parent method implementation: methodName("' + par + '")'); }; // function ChildClass(par1, par2) { ChildClass.super_.apply(this, arguments); this.childField1 = par1; this.childField2 = par2; } // util.inherits(ChildClass, ParentClass); // ChildClass.prototype.methodName = ParentClass.prototype.methodName.override(function(par) { // this.inherited(par); // this.inherited.apply(this, arguments); // console.log('Child method implementation: methodName("' + par + '")'); }); // var childClassInstance = new ChildClass('Lev', 'Nikolayevich'); // childClassInstance.methodName('Tolstoy'); /* : Parent method implementation: methodName("Tolstoy") Child method implementation: methodName("Tolstoy") */
UPD:メソッドのオーバーライドを伴う最適なオプション
xdenserとともに、 super_を使用せず、適用する必要のない、最も高速でかなり簡潔なバージョンが見つかりました。
var util = require('util'); // function override(child, fn) { child.prototype[fn.name] = fn; fn.inherited = child.super_.prototype[fn.name]; } // function ParentClass(par1, par2) { this.parentField1 = par1; this.parentField2 = par2; } // ParentClass.prototype.methodName = function(par) { console.log('Parent method implementation: methodName("' + par + '")'); console.dir({t1:this}) this.parentField3 = par; }; // function ChildClass(par1, par2) { ChildClass.super_.call(this, par1, par2); this.childField1 = par1; this.childField2 = par2; } // util.inherits(ChildClass, ParentClass); // override(ChildClass, function methodName(par) { // methodName.inherited.call(this, par); // console.log('Child method implementation: methodName("' + par + '")'); this.childField3 = par; }); // var childClassInstance = new ChildClass('Lev', 'Nikolayevich'); // childClassInstance.methodName('Tolstoy'); /* : Parent method implementation: methodName("Tolstoy") Child method implementation: methodName("Tolstoy") */
性能比較
望ましいオプションは明らかですが、測定を行う必要があります。 同じ機器で先祖クラスメソッドを呼び出す10,000,000回の呼び出し:
- ClassName.super_.prototype.methodName.apply(this、arguments); 424ミリ秒
- Function.prototype.override(fn)およびthis.inherited(par); 1972ミリ秒。
- Function.prototype.override(fn)およびthis.inherited.apply(this、arguments); 1800ミリ秒
- 最後のオプションは、オーバーライド(子、fn)およびmethodName.inherited.call(this、par)です。 338ミリ秒
ロシア語と英語のコード例とコメントを含むリポジトリ: https : //github.com/tshemsedinov/node-inheritance