現在進行中の JavaScript 本を執筆する際、私は JavaScript 継承システムにかなりの時間を費やし、その過程で古典的なクラス継承をシミュレートするためのさまざまなソリューションを研究しました。これらの技術的ソリューションの中で、base2 と Prototype の実装は私が最も賞賛するものです。
これらのソリューションから、イデオロギー的な意味を持つフレームワークを抽出する必要があります。そのフレームワークは、シンプルで再利用可能で、依存関係から独立している必要があります。使用例は次のとおりです:
var Person = Class. extend ( { init: function (isDancing ) { this. dancing = isDancing; }, dance: function ( ) { return this. dancing; } } ); var Ninja = Person.extend({ init: function(){ this._super( false ); }, dance: function(){ // Call the inherited version of dance() return this._super(); }, swingSword: function(){ return true; } }); var p = new Person(true); p.dance(); // => true var n = new Ninja(); n.dance(); // => false n.swingSword(); // => true // Should all be true p instanceof Person && p instanceof Class && n instanceof Ninja && n instanceof Person && n instanceof Class
注意すべき点がいくつかあります:
結果には非常に満足しています。クラス定義が構造化され、単一継承が維持され、スーパークラス メソッドを呼び出すことができました。
単純なクラスの作成と継承
以下はその実装です (読みやすく、コメントも付いています)。約 25 行です。ご提案を歓迎いたします。
/* Simple JavaScript Inheritance * By John Resig http://ejohn.org/ * MIT Licensed. */ // Inspired by base2 and Prototype ( function ( ) { var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; // The base Class implementation (does nothing) this.Class = function(){}; // Create a new Class that inherits from this class Class.extend = function(prop) { var _super = this.prototype; // Instantiate a base class (but only create the instance, // don't run the init constructor) initializing = true; var prototype = new this(); initializing = false; // Copy the properties over onto the new prototype for (var name in prop) { // Check if we're overwriting an existing function prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){ return function() { var tmp = this._super; // Add a new ._super() method that is the same method // but on the super-class this._super = _super[name]; // The method only need to be bound temporarily, so we // remove it when we're done executing var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } // The dummy class constructor function Class() { // All construction is actually done in the init method if ( !initializing && this.init ) this.init.apply(this, arguments); } // Populate our constructed prototype object Class.prototype = prototype; // Enforce the constructor to be what we expect Class.prototype.constructor = Class; // And make this class extendable Class.extend = arguments.callee; return Class; }; })();
このうち、「初期化する/initを呼ばない」と「_superメソッドの作成」が最も難しいです。次に、誰もがその実装メカニズムをよりよく理解できるように、これについて簡単に紹介します。
初期化
関数プロトタイプの継承方法を説明するために、まず従来の実装プロセスを見てみましょう。これは、サブクラスのプロトタイプ属性が親クラスのインスタンスを指すようにするというものです。以下に示すように:
function Person ( ) { } function Ninja ( ) { } Ninja. prototype = new Person ( ); // Allows for instanceof to work: (new Ninja()) instanceof Person
ただし、ここでの難しい点は、「instanceOf」の効果だけを取得したいだけであり、Person をインスタンス化し、そのコンストラクターを呼び出すという結果は必要ないことです。これを防ぐには、コード内で初期化する bool パラメータを設定します。この値は、親クラスがインスタンス化され、子クラスのプロトタイプ プロパティに設定された場合にのみ true になります。この処理の目的は、実際のインスタンス化中のコンストラクターの呼び出しとデザインの継承の違いを区別し、実際のインスタンス化中に init メソッドを呼び出すことです。
if ( !initializing ) this.init.apply(this, arguments);
スーパーメソッド
継承を使用する場合、最も一般的な要件は、サブクラスがスーパークラスのオーバーライドされたメソッドにアクセスできることです。この実装では、最終的な解決策は、スーパークラス メソッドを指し、サブクラス メソッドでのみアクセスできる一時メソッド (._super) を提供することです。
var Person = Class. extend ( { init: function (isDancing ) { this. dancing = isDancing; } } ); var Ninja = Person.extend({ init: function(){ this._super( false ); } }); var p = new Person(true); p.dancing; // => true var n = new Ninja(); n.dancing; // => false
この機能を実装するには、いくつかの手順が必要です。まず、extend を使用して、基本的な Person インスタンス (その構築プロセスは上で説明したクラス インスタンス) をリテラル オブジェクト (Person.extend() の関数パラメーター) とマージします。マージ プロセス中に、簡単なチェックが行われました。まず、マージされる属性が関数であるかどうかをチェックし、そうである場合は、上書きされるスーパークラス属性も関数であるかどうかをチェックします。両方のチェックが true の場合、このプロパティに対して _super メソッドを準備する必要があります。
追加されたスーパー メソッドをカプセル化するために、匿名クロージャ (関数オブジェクトを返す) がここで作成されることに注意してください。実行環境を維持する必要があるため、関数の実行後にリセットされるように古い this._super を保存する必要があります (同じ名前を付けたくない場合)。誤ってオブジェクト ポインタを失う) 予測できない問題。
次に、スーパー クラス内のオーバーライドされたメソッドのみを指す新しい _super メソッドを作成します。ありがたいことに、_super に変更を加えたりスコープを変更したりする必要はありません。関数の実行環境は関数呼び出しオブジェクト (ポインター this はスーパー クラスを指します) に応じて自動的に変更されるためです。
最後に、リテラル オブジェクトのメソッドを呼び出します。メソッドの実行中に This._super() が使用され、属性 _super が元の状態にリセットされ、関数が終了します。
同じ効果を達成する方法はたくさんあります (以前、super をそれ自体にバインドし、arguments.callee でアクセスするのを見てきました) が、この方法が使いやすさとシンプルさの特徴を最もよく反映していると感じます。
私が完成させた JavaScript プロトタイプに基づく多くの作品の中で、これは私が皆さんと共有するために公開した唯一のクラス継承実装計画です。私は、簡潔なコード (学習しやすく、継承しやすく、ダウンロードが少ない) を全員で議論できるようにする必要があると考えています。そのため、JavaScript クラスの構築と継承を学ぶ人にとって、この実装計画は良いスタートとなります。