v1.6.0 Classオブジェクト
【抜粋】 /* Based on Alex Arnell's inheritance implementation. */ var Class = { create: function() { var parent = null, properties = $A(arguments); if (Object.isFunction(properties[0])) parent = properties.shift(); function klass() { this.initialize.apply(this, arguments); } Object.extend(klass, Class.Methods); klass.superclass = parent; klass.subclasses = []; if (parent) { var subclass = function() { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } for (var i = 0; i < properties.length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) klass.prototype.initialize = Prototype.emptyFunction; klass.prototype.constructor = klass; return klass; } }; Class.Methods = { addMethods: function(source) { var ancestor = this.superclass && this.superclass.prototype; var properties = Object.keys(source); if (!Object.keys({ toString: true }).length) properties.push("toString", "valueOf"); for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") { var method = value, value = Object.extend((function(m) { return function() { return ancestor[m].apply(this, arguments) }; })(property).wrap(method), { valueOf: function() { return method }, toString: function() { return method.toString() } }); } this.prototype[property] = value; } return this; } };
【参考サイト】http://d.hatena.ne.jp/kazu-yamamoto/20071024/1193195233
詳しい解説は上記サイトにありますので、気が付いたことだけメモ。
随分変わってしまいましたね・・・。昔はたった7行だったのに^^;
【参考サイト】http://d.hatena.ne.jp/susie-t/20060710/1152510376
こんなことになったのはすべて、JAVAでいえばsuperにあたる機能を実装しようとしたからです。(逆に言うと、superなんて使わなければもとの7行コードでも十分な気がします)
とりあえず使ってみます。
【例】 var Animal = Class.create({ weight: 10, initialize: function(w){ this.weight = (w || 50); }, eat: function(){ alert("Animal : eat"); }, move: function(){ alert("Animal : move"); } }); var Dog = Class.create(Animal, { initialize: function($super, w){ $super(w); }, eat: function($super){ $super(); alert("Dog : eat"); }, move: function(c){ alert("Dog : move : " + c); } }); var a = new Animal(); alert(a.weight); a.eat(); var d = new Dog(100); alert(d.weight); d.eat(); d.move(5);
【結果(alert表示文字列の順)】 50 Animal : eat 100 Animal : eat Dog : eat Dog : move : 5
Class.create自体にカスタムオブジェクトを渡すことで、すぐにクラス定義が記述できるようになってます。以前はClass.createでオブジェクトを取得し、Object.extend等でprototypeプロパティを設定していました。
継承する場合は、第一引数を親クラス(関数)、第二引数をクラス定義用カスタムオブジェクトにします。
上記Dogクラスのinitialize, eatメソッドの第一仮引数に$superを設定しています。この名前は固定です。ここに設定される関数を実行すると、親クラスにある同名のメソッドが実行されます。ただし、メソッド呼び出し時の引数には、この$superにあたるものは必要ありません。これはprototype.jsが涙ぐましい努力によって、第一仮引数が$superである場合は、そこに親クラスの同名メソッド*1を入れ、第二仮引数以降に実行時引数を入れる、ということをしてくれているからです。
なので、子クラスだからといってすべてのメソッドの第一仮引数が$superである必要はありません。親クラスメソッドを使わないのなら、普通に仮引数を設定すればOKです。
JAVAだとsuper.methodとして別名のメソッドも扱えるのですが、この$superではそれはできません。・・・無理やりやると以下になります。
【参考】無理やり親クラスの別メソッドを実行する。 上記Dog.moveを以下のように変更。 move: function(c){ alert("Dog : move : " + c); this.constructor.superclass.prototype.eat.bind(this)(); //"Animal : eat"と表示される。 }
こっちのほうが汎用性があるかも^^;
以下はコードを見て気が付いたこと。(基本的なことも含めて。)
【抜粋】 var ancestor = this.superclass && this.superclass.prototype;
この場合の動作の理解があいまいでした。これは以下とほぼ同義なんですね。
var ancestor = (this.superclass) ? this.superclass.prototype : null;
または
var ancestor = null; if(this.superclass != null){ ancestor = this.superclass.prototype; }
以下はちょっと悩みました。
【抜粋】 if (!Object.keys({ toString: true }).length)
Object.keysはオブジェクトのプロパティ名の配列を返す拡張メソッド*2。それはいいとして、コードだけ見ると必ずfalseになる気が。
じつはこれは、toStringという名前のプロパティが、列挙可能になるかどうかを判定しているもの。
【参考サイト】http://developer.mozilla.org/ja/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object:propertyIsEnumerable
FireFox、Operaはfalseですが、IEはtrueになります。つまり、IEはtoStringという名前のプロパティを自動的に列挙不能にしているのです(ちなみにvalueOfも)。
【参考】上記は以下と同じ if(!{ toString: true }.propertyIsEnumerable("toString"))
最後に頭が痛かったのは、Function.wrapメソッド。
【抜粋】 var method = value, value = Object.extend((function(m) { return function() { return ancestor[m].apply(this, arguments) }; })(property).wrap(method), { valueOf: function() { return method }, toString: function() { return method.toString() } });
wrapメソッドの定義は以下。
【抜粋】Function.wrapメソッド Object.extend(Function.prototype, { :(省略) wrap: function(wrapper) { var __method = this; return function() { return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); } }, :(省略) });
wrapメソッドは、引数の関数wrapperを実行するクロージャを返却します。wrapper実行時引数は第一引数が自関数、第二引数以降がクロージャ実行時引数です。
ちょっと混乱したのは、「__method.bind(this)」の部分。でも良く考えてみると、これでいいのですね。もし以下のようにすると
wrap: function(wrapper) { var __method = this; return function() { return wrapper.apply(this, [__method].concat($A(arguments))); } },
前述$super実行時、自クラスのプロパティ・メソッドが使用できないことになります。__methodはクロージャ参照なので、そのまま実行するとwindowオブジェクトのメンバとして実行されます。これを解消するためbind(this)が必要なわけです。