前言
我們知道,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還有很多吸引人的功能,我將會在後面的博文中進行介紹。