?? 首先需要說明的是,本文將直接講解創(chuàng)建和使用類的各種技巧,一些基礎(chǔ)的東西不再做解釋,要理解如何在JavaScript中實現(xiàn)面向?qū)ο蟮脑O(shè)計,請先參考《JavaScript.高級程序設(shè)計(第2版)》(前7章)、《javascript.設(shè)計模式》(前四章)、《JavaScript.語言精粹》這三部經(jīng)典之作。
? 在Mootools中使用Class構(gòu)造函數(shù)創(chuàng)建一個類有兩種方式,也就是傳遞兩種不同類型的參數(shù)給構(gòu)造函數(shù),第一種也是標(biāo)準(zhǔn)方式傳遞一個對象字面量,這個對象字面量可以包括你為類添加的所有屬性、方法。例如:
var?Person?=?new?Class({??????????initialize:?function?(name,?age)?{?????????this.name?=?name;?????????this.age?=?age;?????},??????log:?function?()?{?????????console.log(this.name?+?','?+?this.age);?????}?});??var?mark?=?new?Person('mark',?24);?mark.log();?? ? 第二種是傳遞一個普通函數(shù),mootools會自動把這個函數(shù)包裝成只含一個initialize鍵值的對象字面量,然后你可以使用implement方法對類進(jìn)行擴展,例如:
var?Person?=?new?Class(function?(name,?age)?{?????this.name?=?name;?????this.age?=?age;?});?Person.implement('log',?function?()?{?????console.log(this.name?+?','?+?this.age);?});?var?mark?=?new?Person('mark',?24);?mark.log();?? ? 當(dāng)然推薦使用的還是第一種方式,直觀明了嘛。你如果使用標(biāo)準(zhǔn)方式建立了一個類,也是可以使用implement方法對類進(jìn)行擴展的,如果你真的認(rèn)為有必要把一個類的設(shè)計拆成幾個部分的話(例如在使用摻元對象實現(xiàn)多親繼承時,神馬?等等...這個MooTools里實現(xiàn)多親繼承繼承不是使用Implements Mutator嗎,嗯吶,這個在接下來類的繼承中將詳細(xì)講解......),呵呵,至于MooTools內(nèi)部如果對構(gòu)造函數(shù)進(jìn)行解析,有興趣的可看看MooTools 1.4 源碼分析 - Class 修正版。 ?
Implement and Extend ? Implement方法用來為類添加新的方法、屬性。需要注意的是,如果新添加的方法或?qū)傩耘c類中舊有的方法或?qū)傩酝?#xff0c;則會覆蓋類中舊有的方法、屬性。調(diào)用Implement方法有兩種方式,第一種方式傳遞兩個參數(shù),第一個參數(shù)為String類型,存儲要添加的方法或?qū)傩缘拿Q,第二個參數(shù)為方法所對應(yīng)的函數(shù)或?qū)傩运鶎?yīng)的值,這種方式每次只能為類添加一個方法或?qū)傩?#xff1a;
Person.implement('log',?function?()?{?????console.log(this.name?+?','?+?this.age);?});?Person.implement('city',?'深圳');? ? 第二種方式傳遞一個對象字面量參數(shù),把要添加的方法屬性包含在這個對象中,一次添加多個方法、屬性,避免重復(fù)調(diào)用implement:
Person.implement({?????'city':?'深圳',?????'log':?function?()?{?????????console.log(this.name?+?','?+?this.age);?????}?});? ? MooTools關(guān)于Class的官方文檔中只暴露了implement一個方法,其實對類本身進(jìn)行操作的還有一個比較重要的方法extend,這個方法之所以沒有出現(xiàn)在Class的文檔中。這是因為它不是作為Class的特殊方法,而實際上是Type的方法。它的作用是為類創(chuàng)建靜態(tài)成員,靜態(tài)成員關(guān)聯(lián)的是類本身,換句話說,靜態(tài)成員是在類的層次上操作,而不是在實例的層次上操作,每個靜態(tài)成員都只有一份。調(diào)用extend方法的方式同Implement,也是兩種方式。 ? 簡單一點講,implement為實例創(chuàng)建方法和屬性,extend為類本身創(chuàng)建方法和變量,請看下面的例子:
var?Person?=?new?Class(function?(name,?age)?{?????this.name?=?name;?????this.age?=?age;?});?Person.implement({?????instanceMethod:?function?()?{?????????console.log('From?an?instance!');?????}?});?Person.extend({?????classMethod:?function?()?{?????????console.log('From?the?class?itself!');?????}?});??var?mark?=?new?Person('mark',?24);??console.log(typeOf(mark.instanceMethod));??mark.instanceMethod();??console.log(typeOf(mark.classMethod));???console.log(typeOf(Person.classMethod));??Person.classMethod();??console.log(typeOf(Person.instanceMethod));???Person.prototype.instanceMethod();?? ?
私有成員 ? 嚴(yán)格來講,JavaScript中沒有私有成員的概念,所有對象的屬性都是共有的。不過,倒是有一個私有變量的概念,任何在函數(shù)中定義的變量,都可以認(rèn)為是私有變量,因為不能在函數(shù)的外部訪問浙西變量。私有變量包括函數(shù)的參數(shù)、局部變量和在函數(shù)內(nèi)定義的其他函數(shù)。所以我們可以通過使用閉包來為類制造私有成員:
var?Person?=?(function?()?{??????????var?numOfPersons?=?0;???????????var?formatName?=?function?(name)?{?????????return?name.capitalize();?????};??????return?new?Class({?????????initialize:?function?(name,?age)?{?????????????this.name?=?name;?????????????this.age?=?age;?????????????numOfPersons++;?????????},???????????????????log:?function?()?{?????????????console.log(formatName(this.name)?+?','?+?this.age);?????????},??????????getNumOfPersons:?function?()?{?????????????return?numOfPersons;?????????}?????});?})();??var?mark?=?new?Person('mark',?24);?mark.log();??console.log(mark.getNumOfPersons());?? ? 使用這個模式有一個好處就是,私有成員在內(nèi)存中只會存放一份,是由所有實例共享的,不必為每一個實例生成一個副本。但這也延伸出一個問題,來看下面的代碼:
var?Person?=?(function?()?{??????????var?name?=?'';??????return?new?Class({?????????initialize:?function?(v1,?v2)?{?????????????name?=?v1;?????????????this.age?=?v2;?????????},??????????getName:?function?()?{?????????????return?name;?????????},??????????setName:?function?(value)?{?????????????name?=?value;?????????},??????????getAge:?function?()?{?????????????return?this.age;?????????},??????????setAge:?function?(value)?{?????????????this.age?=?value;?????????}?????});?})();??var?mark?=?new?Person('mark',?24);?console.log(mark.getName());??mark.setName('grey');?console.log(mark.getName());??console.log(mark.getAge());???var?john?=?new?Person('john',?18);?console.log(john.getName());??console.log(john.getAge());??console.log(mark.getName());??console.log(mark.getAge());?? ? 這個例子中的Person構(gòu)造函數(shù)(這里指initialize)與getName()和setName()方法一樣,都有權(quán)訪問私有變量name,在這種模式下,變量name就變成了一個靜態(tài)的、有所有實例共享的屬性,也就是說,在一個實例上調(diào)用setName()會影響所有實例,結(jié)果就是所有實例getName()都會返回相同的值,而age是實例變量就不存在這個問題。到底是使用實例變量還是靜態(tài)私有變量,最終還是要視你的需求而定。 ? 當(dāng)然上面這個問題只是針對私有變量的,私有方法就不存在這個問題,相比實例方法會更有效率(從內(nèi)存占用的意義上來說),應(yīng)為它只會被創(chuàng)建一份。 ? 使用閉包還帶來一個問題,多查找作用域鏈中的一個層次,就會在一定程度上影響查找的速度(一般情況下可以忽略不計),魚與熊掌不可兼得啊...... ?
常量 ? 最簡單設(shè)置常量的方法是為類添加一個靜態(tài)屬性,然而靜態(tài)屬性是公有的,類的使用者可以隨時改變它的值,這個樣的操作后果是很嚴(yán)重的。這里我們可以使用前面介紹的為類設(shè)置靜態(tài)私有變量的方式來模擬常量,然后在實例方法中只創(chuàng)建取值器方法而不創(chuàng)建賦值器方法。這樣類的使用者只能使用暴露出來的取值器方法來得到私有變量的值而不能改變它的值。來看下面的代碼:
var?Person?=?(function?()?{??????????var?AGE_UPPER_BOUND?=?32;??????return?new?Class({?????????initialize:?function?(v1,?v2)?{??????????????????????},??????????getAGEUPPERBOUND:?function?(value)?{?????????????return?AGE_UPPER_BOUND;?????????}?????});?})();? ? 如果需要使用多個常量,設(shè)置一個私有的對象字面量來存儲這些常量,然后設(shè)置一個通用的取值器方法來取得這些常量:
var?Person?=?(function?()?{??????????var?constants?=?{?????????AGE_UPPER_BOUND:?32,?????????AGE_LOWER_BOUND:?18?????};??????return?new?Class({?????????initialize:?function?(v1,?v2)?{??????????????????????},??????????getConstants:?function?(name)?{?????????????return?constants[name];?????????}?????});?})();? ?
繼承 ? 繼承的主要好處表現(xiàn)在代碼的重用方面,通過建立類之間的繼承關(guān)系,有些方法我們只需要定義一次就可以了。同樣,如果需要修改這些方法或排查其中的錯誤,那么由于其定義只出現(xiàn)在一個位置,所以非常有利于節(jié)省時間和精力。MooTools實現(xiàn)類的繼承有兩種模式:原型式繼承和多親繼承,原型式繼承由內(nèi)建的Extends Mutator來實現(xiàn),多親繼承由Implements Mutator或implement方法來實現(xiàn)。 ? 原型式繼承這里就不多講了,著重講一下多親繼承。在JavaScript里,因為一個對象只能擁有一個原型對象,所以不允許子類繼承多個超類,不過我們可以利用多個摻元類(minix class)或摻元對象對一個類進(jìn)行擴充,這樣類的實例就可以擁有mixin類(對象)中的方法、屬性,所以這實際上實現(xiàn)了多繼承的效果。通常mixin類(對象)包含一些通用的方法,大家可以看看MooTools里Class.Extras模塊中三個mixin類的定義(Chain、Events、Options)。 ? 這里還需要注意的的一點是,在派生具有私有成員的的類或implement mixin類(對象)時,因為在父類(或mixin)中訪問這些私有變量的方法是公有的,所以他們也會被遺傳下來。所以子類可以間接訪問父類(或mixin)的私有成員,但子類自身的實例方法都不能直接訪問這些私有屬性,而且你也不能在子類中添加能夠直接訪問他們的方法(作用域變量,你想訪問也訪問不了啦,呵呵)。 ? 首先我們先建立一個基類:
var?Animal?=?new?Class({?????initialize:?function?(age)?{?????????this.age?=?age;?????}?});? ? 使用Extends Mutator派生一個子類:
var?Cat?=?new?Class({?????Extends:?Animal,?????initialize:?function?(name,?age)?{?????????this.parent(age);??????????this.name?=?name;?????}?});??var?cat?=?new?Cat('Micia',?20);?console.log(cat.name);??console.log(cat.age);?? ? 利用Implements Mutator擴充一個類,首先建立一個mixin類:
var?Mixin?=?new?Class({?????getName:?function?()?{?????????return?this.name;?????},?????setName:?function?(value)?{?????????this.name?=?value?????}?});??var?Cat?=?new?Class({?????Extends:?Animal,?????Implements:?Mixin,?????initialize:?function?(name,?age)?{?????????this.parent(age);??????????this.name?=?name;?????}?});??var?cat?=?new?Cat('Micia',?20);?console.log(cat.name);??console.log(cat.age);??cat.setName('Dog');?console.log(cat.getName());?? ? 使用implement方法擴充一個類,首先家里一個mixin對象:
?var?objMixin?=?(function?()?{?????var?counter?=?0;??????return?{?????????init:?function?()?{?????????????counter?+=?1;?????????},?????????getCounter:?function?()?{?????????????return?counter;?????????},?????????getAge:?function?()?{?????????????return?this.age;?????????},?????????setAge:?function?(value)?{?????????????this.age?=?value;?????????}?????};?})();??var?Cat?=?new?Class({?????Extends:?Animal,?????Implements:?Mixin,?????initialize:?function?(name,?age)?{?????????this.parent(age);??????????this.name?=?name;?????}?});?Cat.implement(objMixin);??var?Dog?=?new?Class({?????Extends:?Animal,?????Implements:?Mixin,?????initialize:?function?(name,?age)?{?????????this.parent(age);??????????this.name?=?name;?????}?});?Dog.implement(objMixin);??var?cat?=?new?Cat('Micia',?20);?console.log(cat.name);??console.log(cat.age);??cat.setName('湯姆');?console.log(cat.getName());??cat.setAge(12);?console.log(cat.getAge());???cat.init();?console.log(cat.getCounter());???var?dog?=?new?Dog('小狗',?6);?console.log(dog.name);??console.log(dog.age);??dog.setName('布魯托');?console.log(dog.getName());??dog.setAge(8);?console.log(cat.getAge());???dog.init();?console.log(dog.getCounter());??console.log(cat.getCounter());?? ? 大家都看明白了吧,呵呵,不過通過上面的代碼我們引申出另外一個問題,注意上面的Cat類的設(shè)計,我們首先設(shè)計了Extends,然后是Implements,再就是Cat類本身的方法屬性,MooTools內(nèi)部對Class構(gòu)造函數(shù)解析時是按照我們設(shè)計時的順序解析的嗎?答案是按照我們設(shè)計時的順序解釋的。簡單來講MooTools通過for-in對對象進(jìn)行枚舉來遍歷每個成員進(jìn)行解釋的,等等......那個ECMAScript最新版對for-in 語句的遍歷機制又做了調(diào)整,屬性遍歷的順序是沒有被規(guī)定的,也就是說隨機的,那么MooTools是怎樣保證按順序解釋的呢?先看下面這段代碼:
var?obj?=?{?????Waa:?"Waa",?????aa:?'aa',?????68:?'68',?????15:?'15',?????tt:?'tt',?????'-7':?'-7',?????_:?"___",?????online:?true?};?for?(var?k?in?obj)?{?????console.log(k);?}? ? 把它放在各個瀏覽器都執(zhí)行一遍,你會發(fā)現(xiàn)IE、火狐、Safari瀏覽器的JavaScript 解析引擎遵循的是較老的ECMA-262第三版規(guī)范,屬性遍歷順序由屬性構(gòu)建的順序決定,而Chrome、Opera中使用 for-in 語句遍歷對象屬性時會遵循一個規(guī)律,它們會先提取所有 key 的 parseFloat 值為非負(fù)整數(shù)的屬性, 然后根據(jù)數(shù)字順序?qū)傩耘判蚴紫缺闅v出來,然后按照對象定義的順序遍歷余下的所有屬性。其它瀏覽器則完全按照對象定義的順序遍歷屬性。 ? 這下明白了吧,只要你為類設(shè)計的方法、屬性還有Mutator的名稱不為數(shù)字就可以了(當(dāng)然如果你非要有這樣的嗜好,我也只能@#%&$......)。請看下面的代碼:
var?Super?=?new?Class({?????log:?function?()?{?????????console.log('Super');?????}?});??var?Mixin?=?new?Class({?????log:?function?()?{?????????console.log('Mixin');?????}?});??var?Sub?=?new?Class({?????Extends:?Super,?????Implements:?Mixin?});??var?obj?=?new?Sub();?obj.log();?? ? 在這里obj.log()會返回什么呢?對了是'Maxin',這里Sub類首先繼承了Super類,Sub的原型實際就是Super類的一個實例,Super的log方法也就是成了Sub的原型上的一個方法,然后執(zhí)行Implements Mutator 為Sub類的原型擴展了一個Mixin類的實例上的方法,這時Mixin類實例上的log方法就覆蓋了Sub類原型上原來的log方法(繼承自Super類)。 ? 如果把Extends、Implements的順序顛倒一下:
var?Sub?=?new?Class({?????Implements:?Mixin,?????Extends:?Super?});??var?obj?=?new?Sub();?obj.log();?? ? 這時obj.log()會返回什么呢?還是'Maxin'嗎?其實這里返回的是'Super',Why?前面我們介紹了MooTools對Class構(gòu)造函數(shù)解析時是按照我們設(shè)計的順序解析的,所以在這里首先執(zhí)行的是Implements Mutator,它首先為Sub類的原型擴展了一個Mixin類的實例上的log方法,然后才是對超類Super的繼承,因為在JavaScrpt里每個對象只有一個原型,原型式繼承的原理就是超類的一個實例賦予子類的原型,子類原來的原型這時會被超類的實例替換掉,所以這是Sub類原型的引用已經(jīng)指向了超類的實例,而他自己的原型對象這時被消除了,所以之前從Mixin類得來的那個log方法,對不起跟著一起魂飛湮滅了,所以這里返回的是'Super'。 ? 當(dāng)然如果你嫌不過癮,那就在為Sub類添加一個log方法:
var?Sub?=?new?Class({?????Implements:?Mixin,?????Extends:?Super,??????log:?function?()?{?????????console.log('sub');?????}?});?var?obj?=?new?Sub();?obj.log();?? ? 你可以把Sub類的Implements、Extends、log來回顛倒一下看看效果,呵呵,再用implement方法在擴展一個試試:
var?objMixin?=?{?????log:?function?()?{?????????console.log('objMixin');?????}?};??var?Sub?=?new?Class({?????Implements:?Mixin,?????Extends:?Super,??????log:?function?()?{?????????console.log('sub');?????}?});?Sub.implement(objMixin);??var?obj?=?new?Sub();?obj.log();?? ? 呵呵,別暈掉,一切都是為了把問題搞的跟明白不是...... ? 最后不要忘記兩個重要的方法:parent()和protect(),這里就不多說了,在前面的Class源碼分析里有詳細(xì)介紹。 ? 下一篇在詳細(xì)講解一下Mutators。 ? 苦苦的苦瓜 2011-10-07
轉(zhuǎn)載于:https://blog.51cto.com/hmking/682098
總結(jié)
以上是生活随笔為你收集整理的MooTools Class 使用、继承详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。