前言
我們知道,JavaScript中沒有真正的類,它是一種面向原型的語言 。這種語言一個強大的特性就是靈活,實現一個功能可以有很多不同的方式,用不同的編碼風格和技巧。但隨之也帶來了代碼的不可預測和難以維護等問題。代碼量很大時,由於JavaScript 沒有統一的結構,代碼變得很難理解和閱讀,不方便維護和重用。
而像C#這種基於類的面向對象語言,它是強類型的,具有封裝、繼承和多態的OOP基本特征,而且都有標准的編碼約定。它通過強制開發者遵循一系列的原則,讓編寫的代碼更具有可預測性和可擴展性等優點。然而,這種語言卻不能像JavaScript語言一樣可以靈活使用。
這兩種語言各自都有他們的缺點和優點,能不能集合兩者的優點而摒棄它們的缺點呢?在 Ext JS 4 身上似乎能看到這種結合體的影子,讓我們來一看究竟。
命名規則
Ext JS 中命名空間、包和類的層次關系如下圖所示:
基於類、命名空間和文件名的一致的命名規范則,可以幫助我們良好地組織代碼,使其有清晰的層次結構,並且容易閱讀。Ext JS 框架中的所有類都基於這種命名規則,同時官方建議用戶自定義的類也遵照這樣的規則。這個規則如何約定,具體如下:
1)類的命名約定
類名只可含字母和數字,數字雖然允許,但不推薦類的名字中含有數字,除非類名本身是一個專業術語。不要使用下划線(_)、連字符(-)等其他非字母數字的字符。如:
MyCompany.util.Toolbar1 類名含數字,不推薦;
MyCompany.useful_util.Debug_Toolbar 含下划線,不推薦;
MyCompany.util.Base64 Base64 合理。
類應該用包進行分組,至少有一個父層,且頂層一定是命名空間,如:
MyCompany.util.Toolbar 合理;
MyCompany.Application 合理。
命名空間和類的命名遵循CamelCased風格,包括簡寫詞,其它都應小寫,如:
MyCompany.form.action.AutoLoad 合理;
Ext.data.JsonProxy 合理;
Ext.data.JSONProxy 不推薦。
另外,為避免和Ext JS框架內部類發生沖突,用戶自定義類的頂層最好不要用Ext。
2)代碼文件
代碼文件的存放目錄應該和類的全名一一對應,最好是一個類存放一個文件,如:
Ext.util.Observable 應存放在:項目源碼目錄/Ext/util/Observable.js ;
Ext.form.action.Submit 應存放在:項目源碼目錄/Ext/form/action/Submit.js;
MyCompany.chart.axis.Numeric 應存放在:項目源碼目錄/MyCompany/chart/axis/Numeric.js。
3)方法、變量和屬性的命名約定
和類的命名規則類似,方法、變量和屬性的命名只可包含字母和數字,數字是允許的,但不推薦,除非專業術語。
方法和變量都應遵循camelCased風格,包括簡寫。
除了當屬性是常量時需全部大寫,其他情況完全和方法、變量的命名相同。
如:
合理的方法命名:encodeUsingMd5(), getHtml(), getJsonResponse(), parseXmlContent() ;
合理的變量命名:var isGoodName, var base64Encoder, var xmlReader, var httpServer 。
合理的屬性命名:Ext.MessageBox.YES = "Yes", MyCompany.alien.Math.PI = "4.13", MyCompany.Person.Name="Jim" 。
類的聲明/創建
在以前的 Ext JS 版本中用 Ext.extend 來創建一個類,在我另一篇博文ExtJS框架基礎:事件模型及其常用功能中有Ext.extend的例子。在以前的版本中為了遵循命名規范,我們可能這樣來創建一個類:
Ext.ns('My.cool');//Ext.ns是Ext.namespace 的簡寫. My.cool.Window = Ext.extend(Ext.Window, { ... });
老方法中,My.cool.Window必需依賴於另一個已經存在的類創建。但在Ext JS 4中,創建一個類,只需一個方法,且可以不依賴另一個類創建,語法如下:
Ext.define(className, members, onClassCreated);
其中:className是類的全名;members是大括號括起來的一些鍵值對;onClassCreated是一個可選回調函數,當該類創建完畢時調用。來看個實際的例子:
Ext.onReady(function () { Ext.define('My.sample.Person', { name: 'Unknown', constructor: function (name) { if (name) { this.name = name; } }, eat: function (foodType) { alert(this.name + " is eating: " + foodType); } }, function () { alert("Person's instance has created!"); }); var jim = Ext.create('My.sample.Person', 'Jim');//alert("Person's instance has created!") jim.eat("Salad"); // alert("Jim is eating: Salad") });
我們一般用Ext.create()來創建一個類的實例,也可以通過new關鍵字來創建,但推薦使用Ext.create(),Ext.create()能根據依賴關系動態加載創建新類所需要的類。
Ext JS 4中的OOP特性
我們知道,繼承、封裝和多態是OOP的三個基本特性。為讓大家能用大家熟悉的OOP思想編程,Ext JS 4也很好的實現了繼承、封裝和多態。
我們先來看看Ext JS 4 中的繼承,在上面的創建My.sample.Person類的例子中,我們再新建一個 My.sample.Student 類,並讓它繼承 My.sample.Person 類,如下:
Ext.define('My.sample.Student', { //繼承父類:My.sample.Person extend: 'My.sample.Person', //子類的構造器,如果子類有config配置項,則這項必需有。 constructor: function (config) { this.initConfig(config); this.callParent([config]); }, major: function (speciality) { //子類繼承了父類的所有屬性,比如下面的name。 alert(this.name + " majored in: " + speciality); return this; } }); //創建子類實例,父類的構造器先於子類構造器被調用。 var lily = Ext.create('My.sample.Student', 'Lily'); //調用父類中的eat方法 lily.eat('rice'); //alert("Lily is eating: rice") //調用自己的major方法 lily.major('Computer'); //alert("Lily majored in: Computer")
要繼承一個父類,如果子類中有config配置項,則子類的constructor是必需的,不能省略。除了語法上的差別,Ext JS 4 中的繼承非常類似於C#中的繼承。
Ext JS 4 既然有繼承,那么就肯定有OOP的另一個特性:多態。最明顯的是方法的覆蓋。在上面的Student例子中,如果要覆蓋父類的eat方法,只需加上自己的eat配置項:
... eat: function (foodType, tool) { alert(this.name + " is eating " + foodType + " by " + tool+"."); }, ... //調用子類的eat方法 lily.eat('rice', 'tachyon');
在后面講的Mixins部分,其實也是一種多態。
封裝的特性就沒什么好說的了,比如,Ext使用Ext.lib.Event、Ext.EventManager和Ext.EventObject對原生瀏覽器事件進行了封裝,最后給我們用的是一套統一的跨瀏覽器的通用事件接口。使用的時候我們無需關心其內部的實現。
相信對於學過OOP語言的朋友來說,這些特性應該非常好理解,大家在使用Ext JS 4 編程的時候再好好體會吧。下面讓我們繼續深入對 Ext JS 4 類的學習。
讀寫器的實現 - Config
Ext JS 4中的讀寫器意義上類似於C#屬性的讀寫器,只是語法上大不相同。Ext JS 4可通過config配置項實現對私有成員的封裝和對成員的讀寫控制。看下面的例子:
Ext.onReady(function () { Ext.define('My.own.Window', { isWindow: true,//只讀屬性。 config: { //config中的配置項都可通過 getXxx()來讀取值。 title: 'Title Here' }, //構造器,初始化config constructor: function (config) { this.initConfig(config); }, //applyTitle,Title對應config中的配置項title,寫值時內部自動調用。 applyTitle: function (title) { //這里可對寫值做一些控制 if (!Ext.isString(title) || title.length === 0) { alert('Error: Title must be a valid non-empty string'); } else { //在這里可以對寫入的值進行加工。 return title; } } }); var myWindow = Ext.create('My.own.Window', { title: 'Hello World' }); alert(myWindow.getTitle()); // alerts "Hello World" myWindow.setTitle('Something New'); alert(myWindow.getTitle()); // alerts "Something New" myWindow.setTitle(null); // alerts "Error: Title must be a valid non-empty string" });
注意,讀、寫器內部是自動執行的,所以要實現自動讀寫器一定要注意命名。如,config中的title配置項,它的寫值屬性一定是applyTitle,讀值和取值方法分別是getTitle()和setTitle()。
靜態成員 - Statics
我們知道,在C#中,我們可以直接通過類來訪問類中的靜態成員,而不需要實例化。在Ext JS 4 中也可以做到。如下代碼所示:
Ext.onReady(function () { Ext.define('Computer', { statics: { instanceCount: 0, factory: function (brand) { //'this'在靜態方法中代表Computer類本身 return new this({ brand: brand }); } }, config: { brand: null }, constructor: function (config) { this.initConfig(config); //'self'屬性代表Computer類本身 this.self.instanceCount++; } }); //調用靜態函數創建兩個實例。 var dellComputer = Computer.factory('Dell'); var appleComputer = Computer.factory('Mac'); //自動讀取config配置項的brand屬性。 alert(appleComputer.getBrand()); // Alerts "Mac" //調用靜態屬性。 alert(Computer.instanceCount); // Alerts "2" });
他山之石 - Mixins
為了達到功能的復用,Ext JS 4中出現了混用的概念,即把某個類的功能"混"(Mixed)到另一個類之中。比如,我們要創建一個Musician類,讓他具有唱歌和彈吉他的功能,則可以這樣來實現:
Ext.onReady(function () { Ext.define('My.sample.Person', { name: 'Unknown', constructor: function (name) { if (name) { this.name = name; } }, eat: function (foodType) { alert(this.name + " is eating: " + foodType); } }); // 創建會唱歌的類 Ext.define('My.sample.CanSing', { sing: function (songName) { alert("I'm singing " + songName); } }); // 創建會彈吉他的類 Ext.define('My.sample.CanPlayGuitar', { playGuitar: function () { alert("I'm playing guitar"); } }); Ext.define('My.sample.Musician', { extend: 'My.sample.Person', mixins: { //混入其他類中的功能 canSing: 'My.sample.CanSing', canPlayGuitar: 'My.sample.CanPlayGuitar' } }); var lily = Ext.create('My.sample.Musician', "Lily"); //調用其他類中的方法 lily.sing("November Rain"); // alerts "I'm singing November Rain" lily.playGuitar(); // alerts "I'm playing guitar" });
這里,我們還可以對My.sample.CanSing中的sing方法進行重寫實現Musician自己的sing方法,只需要在Musician類中加上自己的sing配置項:
... sing: function () { alert("Attention!"); // this.mixins是指向所有mixins配置項的引用 return this.mixins.canSing.sing.apply(this, arguments); } ...
注意,這里為了演示我把所有類的代碼放在一起寫。在實際項目開發時,遵照Ext的命名約定,每個類應該根據全名放在特定的文件夾和文件中,如上面的My.sample.Person類存放的文件應該是My/sample/Person.js,My.sample.CanSing類存放的文件應該是My/sample/CanSing.js ,而Ext.onReady(...)中的內容應該放在和實際的業務頁面名稱相同的js文件中。
結束語
Ext JS 4 是一個含有300多個類的框架,它具有快速開發、可擴展,容易維護等優點。Ext JS 4 框架讓開發者用他們熟悉面向對象方法進行開發。除了本文介紹的,Ext JS 4還有很多吸引人的功能,我將會在后面的博文中進行介紹。