我們來模仿一下最OO的mootools的繼承機制。它的類都有一個叫做initialize構造方法,這與Java的類都有一個與類名同名的構造方法一樣的道理。只不過,這些叫initialize或init都是借鑒自Prototype,而Prototype那幫人是Ruby出身。為了區別mootools那種汙染原生方法的做法,我把類的構造器命名為variant,並且禁止查看構造方法(像瀏覽器禁止查看原生對象的構造方法那樣)。
<br /> var variant = function (options){<br /> options = options || {};<br /> var initialize = options.initialize || function(){};<br /> var klass = initialize ;<br /> klass.constructor = arguments.callee;<br /> klass.prototype.constructor = klass;<br /> klass.toString = function(){//禁止查看構造方法<br /> return "function variant(){\n [variant code]\n}"<br /> }<br /> return klass;<br /> };</p><p> var Person = variant({initialize:function(age){<br /> this.age = age;<br /> }});<br /> alert(Person)//看不到構造方法的實現<br /> var p = new Person(3);<br /> alert(p)<br /> alert(p.age);<br /> alert(p.constructor);//看不到構造方法的實現<br /> var P = variant({})<br /> var a = new P;<br /> alert(a);<br />
運行代碼
var variant = function (options){ options = options || {}; var initialize = options.initialize || function(){}; var klass = initialize ; klass.constructor = arguments.callee; klass.prototype.constructor = klass; klass.toString = function(){//禁止查看構造方法 return "function variant(){\n [variant code]\n}" } return klass; };
這是一個非常簡單的Factory 方法,用於生產類的。options就是一屬性包,可能裝有我們的類的構造方法,我們要做的是把它提取出來,然後輸送出去。
function factory(a){ a.b = b; a.c = c; return a; }
不過,這就有點像倒爺,我們應該像水果商,從果農收購水果回來封裝一番,才賣出去。這封裝就是往要產生的類添加各種原型方法與類方法。我們進一步打造我們的類工廠,讓生產的類擁有繼承能力,也就是把第一部分的makeBridge 加上去。
<br /> var variant = function (options){<br /> options = options || {};<br /> var initialize = options.initialize || function(){};<br /> var superclass = options.inherit;<br /> delete options.initialize;<br /> delete options.inherit;<br /> var klass = initialize ;<br /> if(superclass){//如果是通過繼承得來的屬性<br /> var bridge = function() {};<br /> bridge.prototype = superclass.prototype;<br /> klass.prototype = new bridge;<br /> klass.prototype.superclass = superclass;<br /> }<br /> for(var i in options){//mixin<br /> klass.prototype[i] = options[i]<br /> }<br /> klass.constructor = arguments.callee;//類的靜態屬性<br /> klass.prototype.constructor = klass;//真正用來建立執行個體的</p><p> klass.toString = function(){<br /> return "function variant(){\n [variant code]\n}"<br /> }<br /> return klass;<br /> };<br /> var Animal = variant({<br /> initialize:function(name){<br /> this.name = name;<br /> },<br /> getName:function(){<br /> return "這是" +this.name;<br /> }<br /> });<br /> var a = new Animal("動物");<br /> alert(a.name)<br /> alert(a.getName())<br /> var Tiger = variant({<br /> inherit:Animal,<br /> initialize:function(name,age){<br /> this.name = name;<br /> this.age =age;<br /> },<br /> getAge : function(){<br /> return this.age;<br /> },<br /> setAge : function(age){<br /> this.age = age;<br /> }<br /> })<br /> var t = new Tiger("老虎",10);<br /> alert(t.age)<br /> alert(t.getName())<br /> t.setAge(11);<br /> alert(t.getAge());<br /> // alert(a.getAge());動物執行個體並沒有此方法<br />
運行代碼
prototype繼承是通過把子類的原型設定成父類的一個執行個體來進行繼承的。因此無論是inherit也好,mixin也好,都是往子類的原型添加東西。打個比方,繼承就是把一大堆屬性與方法直接加在子類的原型上,mixin則相當於把一打屬性與方法逐一加到建構函式的原型。不過在上面Tiger類的構造器寫得有點不好,因為name屬性本來父類就有,子類就不用定義一次。如果父類有許多執行個體屬性,豈不是要寫一大打指派陳述式。應該改為
var Tiger = variant({ inherit:Animal, initialize:function(name,age){ this.superclass(arguments);//this.name = name this.age =age; }, getAge : function(){ return this.age; }, setAge : function(age){ this.age = age; } })
但是這種做法在第三代子類就行不通了,比如我們弄個子類叫IndiaTiger,它比Tiger多出一個類例屬性location。
var IndiaTiger = variant({ inherit:Tiger, initialize:function(name,age,location){ this.superclass(arguments); this.location =location; } });
<br /> var variant = function (options){<br /> options = options || {};<br /> var initialize = options.initialize || function(){};<br /> var superclass = options.inherit;<br /> delete options.initialize;<br /> delete options.inherit;<br /> var klass = initialize ;<br /> if(superclass){//如果是通過繼承得來的屬性<br /> var bridge = function() {};<br /> bridge.prototype = superclass.prototype;<br /> klass.prototype = new bridge;<br /> klass.prototype.superclass = superclass;<br /> }<br /> for(var i in options){//mixin<br /> klass.prototype[i] = options[i]<br /> }<br /> klass.constructor = arguments.callee;//類的靜態屬性<br /> klass.prototype.constructor = klass;//真正用來建立執行個體的</p><p> klass.toString = function(){<br /> return "function variant(){\n [variant code]\n}"<br /> }<br /> return klass;<br /> };<br /> var Animal = variant({<br /> initialize:function(name){<br /> this.name = name;<br /> },<br /> getName:function(){<br /> return "這是" +this.name;<br /> }<br /> });<br /> var Tiger = variant({<br /> inherit:Animal,<br /> initialize:function(name,age){<br /> this.superclass(arguments)<br /> this.age =age;<br /> },<br /> getAge : function(){<br /> return this.age;<br /> },<br /> setAge : function(age){<br /> this.age = age;<br /> }<br /> });<br /> var IndiaTiger = variant({<br /> inherit:Tiger,<br /> initialize:function(name,age,location){<br /> this.superclass(arguments);<br /> this.location =location;<br /> }<br /> });<br /> try{<br /> var i = new IndiaTiger("印度虎",2,"印度");<br /> alert(i.getName());<br /> i.setAge(3);<br /> alert(i.getAge());<br /> alert(i.location);<br /> }catch(e){<br /> alert("報錯了");<br /> alert(e);<br /> }<br />
運行代碼
當new印度虎執行個體時就報錯了,除非其父類的構造器沒有用到this.superclass(arguments)。不用說,問題是出自this,它的不確定性總是為我們惹很多麻煩。這裡的this總為IndiaTiger 的執行個體,因此this.superclass總為Tiger ,也因此我們無法執行個體化Animal 。javascript的執行個體化過程是,有父類先執行個體化父類,然後再到下級子類。由於我們無法通過klass.prototype.superclass擷取Animal,我們可以用klass.superclass來試一下。klass為類,而this為執行個體:
IndiaTiger.superclass.apply(this, arguments);//this為IndiaTiger 的執行個體//arguments為IndiaTiger 構造器的參數對象
但我們不能把前面的IndiaTiger 寫死,因為建立IndiaTiger 這個類時,它還不知自己叫IndiaTiger ,我們可以通過arguments.callee擷取IndiaTiger自身。Tiger的構造相仿。
var IndiaTiger = variant({ inherit:Tiger, initialize:function(name,age,location){ arguments.callee.superclass.apply(this, arguments); this.location =location; } });
我們可以把它再抽取出來,這樣每次就不用寫這麼長的代碼了。
function _super(o, args) {//o為子類的執行個體,agrs為子類構造的arguments對象 return args.callee.superclass.apply(o, args); }
上面的代碼已經假設了,它的構造器總會有inherit這個屬性,但如果沒有豈不是會報錯,另,它還假設了我們的類上面有一個屬性叫superclass,因此我們要在類工廠中做相應的調整。添加或修改如下兩行代碼:
var superclass = options.inherit || Object; klass.superclass = superclass; //類的superclass
<br /> function _super(o, args) {//o為子類的執行個體,agrs為子類構造的arguments對象<br /> return args.callee.superclass.apply(o, args);<br /> }<br /> var variant = function (options){<br /> options = options || {};<br /> var initialize = options.initialize || function(){};<br /> var superclass = options.inherit || Object;<br /> delete options.initialize;<br /> delete options.inherit;<br /> var klass = initialize ;<br /> var bridge = function() {};<br /> bridge.prototype = superclass.prototype;<br /> klass.prototype = new bridge;<br /> klass.prototype.superclass = superclass;//執行個體的superclass<br /> for(var i in options){//mixin<br /> klass.prototype[i] = options[i]<br /> }<br /> klass.constructor = arguments.callee;//類的靜態屬性<br /> klass.prototype.constructor = klass;//真正用來建立執行個體的<br /> klass.superclass = superclass; //類的superclass<br /> // klass.toString = function(){<br /> // return "function variant(){\n [variant code]\n}"<br /> // }<br /> return klass;<br /> };<br /> var Animal = variant({<br /> initialize:function(name){<br /> this.name = name;<br /> },<br /> getName:function(){<br /> return "這是" +this.name;<br /> }<br /> });<br /> var Tiger = variant({<br /> inherit:Animal,<br /> initialize:function(name,age){<br /> _super(this,arguments)<br /> this.age =age;<br /> },<br /> getAge : function(){<br /> return this.age;<br /> },<br /> setAge : function(age){<br /> this.age = age;<br /> }<br /> });<br /> var IndiaTiger = variant({<br /> inherit:Tiger,<br /> initialize:function(name,age,location){<br /> _super(this,arguments);<br /> this.location =location;<br /> }<br /> });<br /> var i = new IndiaTiger("印度虎",2,"印度");<br /> alert(i.getName());<br /> i.setAge(3);<br /> alert(i.getAge());<br /> alert(i.location);<br /> alert(i.superclass)//我們暫時撤去toString方法,它將會彈出其父類的構造器initialize</p><p>
運行代碼
不過每次設定新類的構造器時都要添加一行_super(this,arguments)也太麻煩了,最好把它隱藏起來,內部調用。另,把_super方法放到工廠外,顯得太鬆散,既然也是用來構建類,因此也該把它整合到類工廠中。
function factory(a){ var b = function(){ s(); a(); //*****其他方法 } return b }
也就是說,我們只要這樣設定類的構造器即可:
var IndiaTiger = Variant({ inherit:Tiger, initialize:function(name,age,location){ this.location =location;//★★★★★ } });
<br /><!doctype html><br /><html dir="ltr" lang="zh-CN"><br /> <head><br /> <meta charset="utf-8"/><br /> <title>類</title></p><p> <script type="text/javascript" charset="utf-8"><br /> window.onload = function(){</p><p> var Variant = function (options){<br /> options = options || {};<br /> var initialize = options.initialize || function(){};<br /> var superclass = options.inherit || Object;<br /> var klassname = options.klassname ;<br /> delete options.initialize;<br /> delete options.inherit;<br /> var klass = function() {<br /> superclass.apply(this, arguments);<br /> initialize.apply(this, arguments)<br /> };<br /> var bridge = function() {};//繼承父類<br /> bridge.prototype = superclass.prototype;<br /> klass.prototype = new bridge;<br /> klass.prototype.superclass = superclass;//執行個體的superclass<br /> klass.superclass = superclass; //類的superclass,用於建立父類的執行個體</p><p> for(var i in options){//mixin<br /> klass.prototype[i] = options[i]<br /> }</p><p> klass.constructor = arguments.callee;//類的靜態屬性<br /> klass.prototype.constructor = klass;//用於建立當前類的執行個體</p><p> var getKlassContext = function(c) {<br /> var e = c.toString().replace(/[\s\?]/g,"");<br /> getKlassContext = function() {<br /> return e;<br /> };<br /> return getKlassContext();<br /> };<br /> var getKlassName = function(search,context){<br /> var search = search.toString().replace(/[\s\?]/g,""),<br /> last = search.length >= 50 ? 50 :search.length;<br /> search = search.substring(0,last);<br /> var end = context.indexOf(search),start = end-100;<br /> start = start < 0 ? 0 :start;<br /> var str = context.substring(start,end);<br /> str = str.match(/var(\w+)\=Variant/);<br /> return (str && str[1]) ? str[1] :"Object";<br /> };<br /> if(!klassname){<br /> context = getKlassContext(arguments.callee.caller);<br /> klassname = getKlassName(initialize,context);<br /> if(klassname == "Object"){<br /> throw Error("如果沒有klassname就必須顯式設定initialize");<br /> }<br /> }<br /> klass.klassname = klassname;<br /> klass.prototype.klassname = klassname;<br /> klass.toString = function(){<br /> return ("function "+klassname+"(){\n [variant code]\n}");<br /> }</p><p> return klass;<br /> };</p><p> var Animal = Variant({<br /> initialize:function(name){<br /> this.name = name;<br /> },<br /> getName:function(){<br /> return "這是" +this.name;<br /> }<br /> });<br /> var Tiger = Variant({<br /> inherit:Animal,<br /> initialize:function(name,age){<br /> this.age =age;<br /> },<br /> getAge : function(){<br /> return this.age;<br /> },<br /> setAge : function(age){<br /> this.age = age;<br /> }<br /> });<br /> var IndiaTiger = Variant({<br /> inherit:Tiger,<br /> initialize:function(name,age,location){<br /> this.location =location;<br /> }<br /> });</p><p> var i = new IndiaTiger("印度虎",2,"印度");<br /> alert(i.klassname);<br /> alert(i.getName());<br /> i.setAge(3);<br /> alert(i.getAge());<br /> alert(IndiaTiger);<br /> alert(Tiger);<br /> }</p><p> </script><br /> </head><br /> <body><br /> <pre><br /> var Animal = Variant({<br /> initialize:function(name){<br /> this.name = name;<br /> },<br /> getName:function(){<br /> return "這是" +this.name;<br /> }<br /> });<br /> var Tiger = Variant({<br /> inherit:Animal,<br /> initialize:function(name,age){<br /> this.age =age;<br /> },<br /> getAge : function(){<br /> return this.age;<br /> },<br /> setAge : function(age){<br /> this.age = age;<br /> }<br /> });<br /> var IndiaTiger = Variant({<br /> inherit:Tiger,<br /> initialize:function(name,age,location){<br /> this.location =location;<br /> }<br /> });</p><p> var i = new IndiaTiger("印度虎",2,"印度");<br /> alert(i.klassname);<br /> alert(i.getName());<br /> i.setAge(3);<br /> alert(i.getAge());<br /> alert(IndiaTiger);<br /> alert(Tiger);<br /> </pre><br /> </body><br /></html><br />
運行代碼