Javascriptの簡単な継承ツールの実現原理を簡単に説明します
12751 ワード
背景
私は自分のゲームを开発することをとても望んでいるので、余暇时间はずっと自分でいくつか面白くて面白いものが出てくることができるかどうかを考えていて、最近steamの上で多くの独立したゲームの爆発に従って、自分がまた燃え始めたと感じて、だからまたとても昔の1つの2 dエンジンを拾って、余暇の时间を利用して全力で开発することを决めました.
このエンジンはhtml 5 canvasとWebGLに基づいており、主にWebGLをサポートしている.canvasは性能の問題で現在限られたサポートを提供するつもりだ.以前は簡単なエンジンを作ったことがあるので、今回は構造の良いエンジンを作るつもりだ.今回の目標は高性能と使いやすさ、そして良好な構造と拡張性であり、これらの面を両立させてプロジェクトを行うことは非常に困難であるため、徐々に実現するとともに、コードをできるだけ短くすることも重要である.
エンジンやグラフィック画像に関する知識については、c-dog私も、多くの知識を補う必要があるので、この方面の知識はあまり言わないが、今回の重点はPIXI書いたcocos2dx-jsで、私のフレームワークの中で使用が簡単で、副作用がなく他の依存のない継承メカニズムを実現する必要があるため、だから私は自分のクラスの継承方法を設計する時偶然大神のこのブログを発見して、このブログの中で1種の簡単なクラスの継承方法を設計して、実現しても使用しても、すべてとても簡単で、ネット上の多くの人もすべてこのブログを転載して、しかし誰もこのコードを分析するために立っていません.だから今日私は简単にこのコードを剖析して分析するつもりで、javascriptの基础知识についてJohn Resigこの本を参照してください、このブログは基础知识がありませんよ、javascriptの高级な知识に対してまだしっかりしていないならば、ブログを参照して强化することができます.
Jon Resigのソースコードプロファイリング
このコードは確かに非常に短く、jsの特徴(プロトタイプ、閉パッケージ)を十分に利用しています.主な原理はjsのプロトタイプを利用しています.新しいオブジェクトの構造方法を生成するときに、作成する方法を一時的な構造方法で置き換え、extend方法に提供したパラメータを徐々に分析した後、一時的な構造方法に追加しました.最後に私たちに返されたのは、その一時的な構造方法であり、原理の背後には小さな詳細がたくさんあります.以下、徐々に説明します.
this.Class = function(){};
分析:
上記の3行のコードは主に3つの問題を解決するためであり、変数initializingは継承関係がある場合、親の初期化タイミングの問題を解決するためである.ターゲット構築方法のプロトタイプを構築する際に親を初期化することを望んでいないため、このタイプが定義された後、外部でそのクラスを初期化する際に親を初期化する必要がある.この一時変数は簡単にこの問題を解決し、まずこの変数をfalseに設定し、それから目標構造関数を構築し、構築が完了した後、この変数をtrueに設定し、最後に閉パッケージに保存し、私たちが新しいクラスのためにオブジェクトを生成するとき、親も同時に初期化されます.
この変数を単独で見ると抽象的で、extend関数のコードを結合する必要があります.
fnTestという正規表現では、ibmの文章を含め、このコードを分析する多くの人がこの問題を回避しています.他の部分は詳しく説明されていますが、この正規表現だけは説明されていません.この正規の意味は、私たちが生成するクラスの構造関数の親と同じ名前の方法をテストすることです.テストの理由は、子クラスに親と同じ名前のメソッドが存在し、関数定義文字列に_superという文字列では,このメソッドが親クラスの同名メソッドを呼び出したと考えられ,親クラスと子クラスの同名メソッドがどのように構築されるかについては後述する.しかし、なぜxyzという正則をテストするのでしょうか.xyzってどういう意味ですか?実はここでxyzは何の意味もありません.xyzをabcに変えるのも同じです.xyzのコードをよく見ると、
/xyz/.test(function(){xyz;});/xyz/.test("function(){xyz}");
異なる、1つは直接テストする関数オブジェクトで、1つは文字列で、私たちが1つの関数を得る時、外部で彼の定義文字列を得ることができなくて、このようにテストして、互換性のためで、あるブラウザのjsエンジンは、正則で直接関数オブジェクトをテストすることができないので、もしわざとテストに成功したら、子クラスのメソッドが親クラスの同名メソッドを呼び出したことを示します(約束通り、実際に呼び出されたとは限りません).superは私たち自身が定義したキーワードで、テストに失敗した場合、私たちはすべての同名メソッドを親メソッドを呼び出した同名メソッドとして処理します.
このClass空構造関数は,我々が組み立てる必要がある構造関数の原型であり,我々がextend法のパラメータに提供する方法であり,最終的にはこのクラスに組み立てる必要がある.ここでthisはwindowまたはnodejsのグローバルオブジェクトを指します.だからここに書くか書かないかは関係ないが、作者は少し白を惑わそうとしているような気がする.
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;
次に、extend関数体の内部に直接入ります.この継承システムのエッセンスはここにあります.このコードを理解するには、まずこのコードの多くがthisがどのオブジェクトを指しているかを理解しなければなりません.
最初の文_superはthisの原型を指しています.ここでthisは2つのケースがあります.継承が存在しない場合、thisはClassそのもの、つまり彼自身を指しています.Classを定義する場所のthisはwindowを指しています.nodejsではグローバル空間を指しています.継承が存在する場合、thisは親自身を指しています.だから、thisを使います.prototypeは、常に正しいプロトタイプを得ることができます.このプロトタイプは、私たちが定義しているオブジェクトのプロトタイプではなく、彼の中のサブクラスと同じ名前の方法が必要です.ここではキャッシュとして使用します.
ここでこそ、現在のクラスのオブジェクトを生成する真のプロトタイプが必要です.ターゲット構造関数のプロトタイプを指す必要があります.
上のコードが最も核心的で、最も理解しにくいのは、閉じたものに関連しているため、この場所は閉じたパッケージを使用しなければならない.閉じたパッケージを使用してこそ、関数の外部の変数を含めて一緒にパッケージすることができるからだ.
forループは私たちが提供した様々な方法を遍歴し、親と同名で、サブクラスに親メソッドを呼び出す方法がある場合、著者は閉パッケージを使用して小さな動作を行い、著者はまず現在のオブジェクトの_superを一時変数に入れ、次に_superは、親クラスの同名メソッドを指し、現在のthisに置き換えて直接実行し、結果を返し、prototype、すなわち本当に必要なプロトタイプにこの閉パッケージ関数を付与することで、親クラスと同名で内部でthisを呼び出すマジックの効果を奏することができる.super()関数のメソッドの場合、これはthis.superは、私たちが
の中でsuperが指すのは親のinitです
上の場合、this.superは、親クラスのreadメソッドを指します.
この魔術の方法の根源は上の閉包にあり、それぞれの原型の方法の中で、this.superはすでに動的に生成されていないので、パッケージを閉じているので、この効果を達成することができます.
最後にこのいくつかのコードは、現場を整理したと言ってもいいですが、改造されたClassオブジェクトはまだ完全に私たちが望んでいるものではありません.その原型のconstructorは私たちに変更されたので、直接変更すればいいです.そうしないと、私たちが生成したクラスはタイプが間違っています.instanceofを使用したときに得られた結果も間違っています.最後に、このオブジェクトのextendを私たちのextendメソッドにバインドします.
まとめ
実はJohn Resigの方法に驚く必要はありません.なぜなら、私はそのコードを参照する前に、彼のコードよりも前の方法を書いたからです.jsのpythonのような修飾器の技術を使って、現在のサブクラスの構造方法をprototypeに全部入れました.親の同名の方法は、すべて現在のオブジェクトの__に入れました.super__属性では、オブジェクトの属性はすべて修飾器として書かれており、修飾器の役割は、その属性が親属性なのか子属性なのかを判断することであり、継承は本来、子が親を含むことを実現し、コードの重複を避ける問題であるからである.
私のやり方ではsuperは固定的な方法を指さず、このような方法でアクセスできます.
私のsuperは初期化フェーズで値をバインドしません.また、子メソッドで親メソッドの同名または非同名のメソッドを呼び出すこともできます.
しかし、これは必須ではありません.私の方法は問題を説明するためだけで、対象に向かってこのような複雑な実現は必要ありません.だから、John Resigに基づく方法の改善版を使うことにしました.以下はJAvascript高度なプログラム設計です.ご意見をお待ちしています.
} );
私は自分のゲームを开発することをとても望んでいるので、余暇时间はずっと自分でいくつか面白くて面白いものが出てくることができるかどうかを考えていて、最近steamの上で多くの独立したゲームの爆発に従って、自分がまた燃え始めたと感じて、だからまたとても昔の1つの2 dエンジンを拾って、余暇の时间を利用して全力で开発することを决めました.
このエンジンはhtml 5 canvasとWebGLに基づいており、主にWebGLをサポートしている.canvasは性能の問題で現在限られたサポートを提供するつもりだ.以前は簡単なエンジンを作ったことがあるので、今回は構造の良いエンジンを作るつもりだ.今回の目標は高性能と使いやすさ、そして良好な構造と拡張性であり、これらの面を両立させてプロジェクトを行うことは非常に困難であるため、徐々に実現するとともに、コードをできるだけ短くすることも重要である.
エンジンやグラフィック画像に関する知識については、c-dog私も、多くの知識を補う必要があるので、この方面の知識はあまり言わないが、今回の重点はPIXI書いたcocos2dx-jsで、私のフレームワークの中で使用が簡単で、副作用がなく他の依存のない継承メカニズムを実現する必要があるため、だから私は自分のクラスの継承方法を設計する時偶然大神のこのブログを発見して、このブログの中で1種の簡単なクラスの継承方法を設計して、実現しても使用しても、すべてとても簡単で、ネット上の多くの人もすべてこのブログを転載して、しかし誰もこのコードを分析するために立っていません.だから今日私は简単にこのコードを剖析して分析するつもりで、javascriptの基础知识についてJohn Resigこの本を参照してください、このブログは基础知识がありませんよ、javascriptの高级な知识に対してまだしっかりしていないならば、ブログを参照して强化することができます.
Jon Resigのソースコードプロファイリング
/* 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;
};
})();
このコードは確かに非常に短く、jsの特徴(プロトタイプ、閉パッケージ)を十分に利用しています.主な原理はjsのプロトタイプを利用しています.新しいオブジェクトの構造方法を生成するときに、作成する方法を一時的な構造方法で置き換え、extend方法に提供したパラメータを徐々に分析した後、一時的な構造方法に追加しました.最後に私たちに返されたのは、その一時的な構造方法であり、原理の背後には小さな詳細がたくさんあります.以下、徐々に説明します.
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
this.Class = function(){};
分析:
上記の3行のコードは主に3つの問題を解決するためであり、変数initializingは継承関係がある場合、親の初期化タイミングの問題を解決するためである.ターゲット構築方法のプロトタイプを構築する際に親を初期化することを望んでいないため、このタイプが定義された後、外部でそのクラスを初期化する際に親を初期化する必要がある.この一時変数は簡単にこの問題を解決し、まずこの変数をfalseに設定し、それから目標構造関数を構築し、構築が完了した後、この変数をtrueに設定し、最後に閉パッケージに保存し、私たちが新しいクラスのためにオブジェクトを生成するとき、親も同時に初期化されます.
この変数を単独で見ると抽象的で、extend関数のコードを結合する必要があります.
fnTestという正規表現では、ibmの文章を含め、このコードを分析する多くの人がこの問題を回避しています.他の部分は詳しく説明されていますが、この正規表現だけは説明されていません.この正規の意味は、私たちが生成するクラスの構造関数の親と同じ名前の方法をテストすることです.テストの理由は、子クラスに親と同じ名前のメソッドが存在し、関数定義文字列に_superという文字列では,このメソッドが親クラスの同名メソッドを呼び出したと考えられ,親クラスと子クラスの同名メソッドがどのように構築されるかについては後述する.しかし、なぜxyzという正則をテストするのでしょうか.xyzってどういう意味ですか?実はここでxyzは何の意味もありません.xyzをabcに変えるのも同じです.xyzのコードをよく見ると、
/xyz/.test(function(){xyz;});/xyz/.test("function(){xyz}");
異なる、1つは直接テストする関数オブジェクトで、1つは文字列で、私たちが1つの関数を得る時、外部で彼の定義文字列を得ることができなくて、このようにテストして、互換性のためで、あるブラウザのjsエンジンは、正則で直接関数オブジェクトをテストすることができないので、もしわざとテストに成功したら、子クラスのメソッドが親クラスの同名メソッドを呼び出したことを示します(約束通り、実際に呼び出されたとは限りません).superは私たち自身が定義したキーワードで、テストに失敗した場合、私たちはすべての同名メソッドを親メソッドを呼び出した同名メソッドとして処理します.
this.Class = function(){};
このClass空構造関数は,我々が組み立てる必要がある構造関数の原型であり,我々がextend法のパラメータに提供する方法であり,最終的にはこのクラスに組み立てる必要がある.ここでthisはwindowまたはnodejsのグローバルオブジェクトを指します.だからここに書くか書かないかは関係ないが、作者は少し白を惑わそうとしているような気がする.
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;
次に、extend関数体の内部に直接入ります.この継承システムのエッセンスはここにあります.このコードを理解するには、まずこのコードの多くがthisがどのオブジェクトを指しているかを理解しなければなりません.
最初の文_superはthisの原型を指しています.ここでthisは2つのケースがあります.継承が存在しない場合、thisはClassそのもの、つまり彼自身を指しています.Classを定義する場所のthisはwindowを指しています.nodejsではグローバル空間を指しています.継承が存在する場合、thisは親自身を指しています.だから、thisを使います.prototypeは、常に正しいプロトタイプを得ることができます.このプロトタイプは、私たちが定義しているオブジェクトのプロトタイプではなく、彼の中のサブクラスと同じ名前の方法が必要です.ここではキャッシュとして使用します.
var prototype = new this();
ここでこそ、現在のクラスのオブジェクトを生成する真のプロトタイプが必要です.ターゲット構造関数のプロトタイプを指す必要があります.
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];
}
上のコードが最も核心的で、最も理解しにくいのは、閉じたものに関連しているため、この場所は閉じたパッケージを使用しなければならない.閉じたパッケージを使用してこそ、関数の外部の変数を含めて一緒にパッケージすることができるからだ.
forループは私たちが提供した様々な方法を遍歴し、親と同名で、サブクラスに親メソッドを呼び出す方法がある場合、著者は閉パッケージを使用して小さな動作を行い、著者はまず現在のオブジェクトの_superを一時変数に入れ、次に_superは、親クラスの同名メソッドを指し、現在のthisに置き換えて直接実行し、結果を返し、prototype、すなわち本当に必要なプロトタイプにこの閉パッケージ関数を付与することで、親クラスと同名で内部でthisを呼び出すマジックの効果を奏することができる.super()関数のメソッドの場合、これはthis.superは、私たちが
init::function(){
this._super();
}
の中でsuperが指すのは親のinitです
read:function(){
this._super();
}
上の場合、this.superは、親クラスのreadメソッドを指します.
この魔術の方法の根源は上の閉包にあり、それぞれの原型の方法の中で、this.superはすでに動的に生成されていないので、パッケージを閉じているので、この効果を達成することができます.
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;
最後にこのいくつかのコードは、現場を整理したと言ってもいいですが、改造されたClassオブジェクトはまだ完全に私たちが望んでいるものではありません.その原型のconstructorは私たちに変更されたので、直接変更すればいいです.そうしないと、私たちが生成したクラスはタイプが間違っています.instanceofを使用したときに得られた結果も間違っています.最後に、このオブジェクトのextendを私たちのextendメソッドにバインドします.
まとめ
実はJohn Resigの方法に驚く必要はありません.なぜなら、私はそのコードを参照する前に、彼のコードよりも前の方法を書いたからです.jsのpythonのような修飾器の技術を使って、現在のサブクラスの構造方法をprototypeに全部入れました.親の同名の方法は、すべて現在のオブジェクトの__に入れました.super__属性では、オブジェクトの属性はすべて修飾器として書かれており、修飾器の役割は、その属性が親属性なのか子属性なのかを判断することであり、継承は本来、子が親を含むことを実現し、コードの重複を避ける問題であるからである.
私のやり方ではsuperは固定的な方法を指さず、このような方法でアクセスできます.
init:function(){
this.super.init(arguments);
}
私のsuperは初期化フェーズで値をバインドしません.また、子メソッドで親メソッドの同名または非同名のメソッドを呼び出すこともできます.
しかし、これは必須ではありません.私の方法は問題を説明するためだけで、対象に向かってこのような複雑な実現は必要ありません.だから、John Resigに基づく方法の改善版を使うことにしました.以下はJAvascript高度なプログラム設計です.ご意見をお待ちしています.
// author : youngershen
// email : [email protected]
//
var SUPERCLASS = function(){};
(function(SUPERCLASS){
SUPERCLASS = SUPERCLASS || {};
var CLASS = SUPERCLASS;
var set_builder_func = function(prop){
return function(value){
if(this.super[prop] == undefined){
this[prop + '_value'] = value;
}else if(this[prop] == undefined){
this.super[prop] = value;
}
};
};
var get_builder_func = function(that, prop){
return function(){
return (function(prop){
return function(){
if(that[prop + '_value'] != undefined){
return that[prop + '_value'];
}else{
return that.super[prop];
}
}();
})(prop);
}
};
CLASS.extend = function(prop){
var this_super = {};
var initializing = false;
var METACLASS = function(){
if(initializing && this.init && arguments.length != 0){
this.super = this_super;
this.init.apply(this, arguments);
for(var prop in this){
if((typeof this[prop]) != 'function'){
if(this.super[prop] == undefined && prop != 'super'){
this[prop + '_value'] = this[prop];
}
if(prop == 'super'){
for(var _prop in this[prop]){
if((typeof this[prop][_prop]) != 'function'){
Object.defineProperty(this, _prop,{
enumerable:true,
configurable:true,
set:set_builder_func(_prop),
get:get_builder_func(this, _prop)
});
}
}
}else{
Object.defineProperty(this,prop, {
enumerable:true,
configurable:false,
set:set_builder_func(prop),
get:get_builder_func(this, prop)
});
}
}
}
}
};
var supertype = this.prototype;
var prototype = new this();
initializing = true;
if((this instanceof Function)){
for(var property in prop){
if(typeof prop[property] == "function" && typeof supertype[property] == 'function'){
this_super[property] = supertype[property]
prototype[property] = prop[property];
}else if((typeof prop[property] == 'function')){
prototype[property] = prop[property];
}
}
METACLASS.extend = arguments.callee;
METACLASS.prototype = prototype;
METACLASS.constructor = METACLASS;
supertype = null;
prototype = null;
return METACLASS;
};
}
})(SUPERCLASS);
var Person = SUPERCLASS.extend({
init:function(name,age){
this.name = name;
this.age = age;
},
say:function(){
console.log(this.name);
console.log(this.age)
}
});
var Student = Person.extend({
init:function(name, age, id){
debugger;
this.super.init(name, age);
this.id = id;
},
say:function(){
this.super.say();
console.log(this.id);
},
get_id:function(){
console.log(this.id);
}
} );
var GoodStudent = Student.extend({
init:function(name, age, id, score){
console.log(this.super.init);
debugger;
this.super.init();
this.score = score;
},
score:function(){
conosole.log(this.score);
}
});
var p = new Person('younger', '28');
console.log(p.name);
console.log(p.age);
p.say();
console.log(p);
/*
var s = new Student('lily', 15, '123456');
s.say();
s.get_id();
console.log(s)
*/
var gs = new GoodStudent('hehe', 11, '123', 100);
console.log(gs);
gs.score();