Sencha 與 ExtJS
前面的章節我們有提到過,Sencha 是基於 ExtJS 的設計 (Ext 4),然而在 JavaScript 這樣的弱型別程式語言中,最缺乏的就是物件導向的實作。ExtJS 為了解決這樣的問題,因此在 JavaScript 中創造了「偽物件導向」的架構。這樣一來在 JavaScript 也能有類別與繼承能力,讓程式碼的維護性大幅提昇。在這個單元我們會來介紹 ExtJS 系統中的物件基本用法,在開始閱讀 Sencha 程式碼之前要需要有點概念比較好。
ExtJS Object-Oriented 類別系統介紹
為什麼說 ExtJS 是「偽物件導向」呢?因為 JavaScript 本身是沒有類別機制的,ExtJS 透過一些設計模式來實現類似物件導向的程式語言開發方式,讓程式設計師比較容易寫出好維護可重用的程式碼。JavaScript 是一種很有彈性的程式語言,我常說「JavaScript 實在是弱的很強」!我們先來介紹一下 ExtJS Object-Oriented 類別系統提供了哪些特性:
-
- Class 類別
- Object Instance 實體化物件
- Constructor 建構子
- Static Member 靜態成員
- Extend / Override 繼承與覆寫
- Class Dependency 類別相依
上述的特性是比較重要的幾項,我們開始來介紹 Ext Class System。各位可以使用上一個單元介紹所產生的的 Sencha Project,直接將程式碼撰寫在 app.js 前面即可。接著我們來先來看看要如何定義一個新的 Class 類別,程式碼如下:
// 利用 Ext.define() 定義一個簡單的 Class Ext.define('Animal', { // config 是 Ext 的保留參數,可以用來定義 Properties config: { name: null }, // 這裡是建構子 constructor: function(config) { this.initConfig(config); }, // 自定的函式 speak: function() { alert('ohwoo...'); } });
接下來我們想上述的類別進行實體化,也就是 new 的意思,程式碼如下(一樣寫在 app.js 即可):
// 利用 Ext.create() 實體化物件 var dog = Ext.create('Animal', { name: 'lucky' }); dog.speak(); // alerts 'ohwoo...' // config 可動態產生 getter 與 setter alert(dog.getName()); // alerts 'lucky'
下一步我們來練習一下 ExtJS 的物件繼承能力
// 繼承 Animal,並且覆寫 speak function Ext.define('Human', { extend: 'Animal', speak: function() { alert('I am ' + this.getName()); } }); var sj = Ext.create('Human'); // new Human sj.setName('SJ'); // 設定 Name 為 SJ sj.speak(); // alerts 'I am SJ'
如果您運氣好的話,就可以看到跟我一樣的執行結果,如下:
程式設計時也常用到靜態函式(類別方法),我們接續來介紹如何定義 Static Member 與 Function,範例程式如下:
// 透過 static 關鍵字來宣告靜態變數或方法 Ext.define('StaticHuman', { extend : 'Animal', statics : { instanceCount : 0, // 實作 Factory Design Pattern factory : function(name) { var obj = new this(); obj.setName(name); return obj; }, }, constructor : function(config) { this.initConfig (config); // 利用 'self' 表示自身靜態類別 this.self.instanceCount++; } }); var sj, david; // 產生一個 Instance 傳給給 sj 這個變數 sj = StaticHuman.factory('SJ'); // 產生一個 Instance 傳給給 david 這個變數 david = StaticHuman.factory('David'); alert(sj.getName()); // alert 'SJ' alert(david.getName()); // alert 'David' alert(StaticHuman.instanceCount ); // alert '2'
Sencha 檔案與類別命名慣例
上一節的單元我們自行定義了幾個類別,所有的程式語言都有所謂的命名慣例,遵照命名慣例也是一個好的工作習慣。在 ExtJS Class System 中也有類似 Java 的 Package 概念,接下來我們要介紹幾項重要的程式命名慣例。
類別命名慣例 Naming Conventions
-
- 類別名稱使用「大駝峰模式」,舉例:MyClass, Human
- 類別變數、函式與成員使用「小駝峰模式」,舉例:myProperty, flag, getName()
- 套件名稱第一階使用「大駝峰模式」,第二階使用「小寫英文」,舉例:One.two, MyApp.controller.pad
整理來說,整個 Class 就變成 MyApp.custom.Human 這樣的完整類別名稱,其中 MyApp.custom 就像是 Java 中的套件名稱,可以用來區分與歸類我們的設計。
檔案命名規範 Source File
一般來說我們習慣一個類別用一支檔案來存放,在 ExtJS 中也是一樣,假設我們的類別名稱為 MyApp.custom.Human 那麼我們可以將程式撰寫在 /app/custom/Human.js 這一個檔案中。再舉另一個例子:假設類別名稱為 MyApp.controller.pad.Main 那麼檔案就在 /app/custom/pad/Main.js 這裡。此外我們還可以偷偷觀察一下整個 Ext Library 的檔案,在 \touch\src 中也可以看到一樣的實作方式。
我們先把前面寫的 Animal 與 Human 這兩個類別改為 MyApp.custom.Animal 與 MyApp.custom.Human,並且變成獨立的 Source File 並且分別放在 /app/custom/Animal.js 與 /app/custom/Human.js,修改後的程式範例如下:
// File: /app/custom/Animal.js Ext.define('MyApp.custom.Animal', { config: { name: null }, constructor: function(config) { this.initConfig(config); }, speak: function() { alert('ohwoo...'); } });
// File: /app/custom/Human.js Ext.define('MyApp.custom.Human', { // 繼承 MyApp.custom.Animal extend: 'MyApp.custom.Animal', // 覆寫 speak function speak: function() { alert('I am ' + this.getName()); } });
Ext Class Loader
上一節將程式放在獨立的檔案後,那我們要如何載入這些檔案呢?總不能每一支都寫到 app.json 中吧,更不會是在 index.html 載入這些檔案。其實 Ext 內建了自動載入機制 (類似 PHP Autoloader),我們先觀察一下 app.js 這一支檔案,在一開始的時候有以下幾段程式碼,目的就是宣告當遇到哪些 Class 的名稱要讀取那個目錄中的檔案,Ext.Loader 程式範例如下:
//<debug> Ext.Loader.setPath({ 'Ext': 'touch/src', 'MyApp': 'app' }); //</debug>
從上述的設定就可以明白為什麼 MyApp.custom.Human 這個類別程式檔要放在 /app/custom/Human.js 這裡了。因此我們也可以創造自己的函式庫,再透過 Ext.Loader 自動載入到我們的 App 中。
類別相依關係
在 ExtJS 的類別系統中,若是要引用其他 Class 必須要在 requires 這個設定中指定 Class Name。這樣的動作主要的目的為告訴 Runtime 類別之間的相依關係,透過 Ext.Loader 配合上一個章節要介紹的 Source File 規則就可以自動載入需要的 JavaScript 檔案。其實還有一個很重要的原因,當未來 Sencha Touch Application 要透過 Sencha CMD 建構 (Build) 時,才能依照類別引用順序來載入相依類別,我們在一開始撰寫 Sencha 時就應該養成寫好 requires 的習慣。
那我們就來練習一下 requires 的用法,我們先修改 app.js 中的 Ext.application 中的 launch function,這裡是整的 App 的進入點。下面的程式就是修改後的 app.js,移除了不需要的程式碼:
//<debug> Ext.Loader.setPath({ 'Ext': 'touch/src', 'MyApp': 'app' }); //</debug> Ext.application({ name: 'MyApp', // 設定相依的類別 requires: [ 'MyApp.custom.Human' ], // 啟動點 launch: function() { // 實體化 var sj = Ext.create('MyApp.custom.Human', { name: 'SJ' }); sj.speak(); // alert } });
執行後我們可以看到 Human.speak() 正確的 alert 出訊息。我們在來修改一下 MyApp.custom.Human.js 將原本 Native alert 改成用 Ext.Msg 來實作(比較美)修改後的程式碼如下:
Ext.define('MyApp.custom.Human', { // 繼承 MyApp.custom.Animal extend: 'MyApp.custom.Animal', // 設定相依的類別名稱,這裡比較不同 Ext.Msg 需要 Require Ext.MessageBox requires: [ 'Ext.MessageBox' ], // 覆寫 speak function speak: function() { // 改用 Ext.Msg.alert Ext.Msg.alert('I am ' + this.getName()); } });
執行後我們便可以看到以下執行畫面:
此外一提,extend 繼承的的類別是不需要寫在 requires 中的,避免多此一舉。
系列文章
- Web App 行動開發 (1) - Web App 開發介紹
- Web App 行動開發 (2) - 如何設計出一個好用的 Web App?
- Web App 行動開發 (3) – Sencha Touch 開發環境建置
- Web App 行動開發 (4) – Sencha Touch 基本架構介紹
- Web App 行動開發 (5) – Sencha Touch 物件導向類別系統介紹
- Web App 行動開發 (6) – Sencha Touch 初探 Application
- Web App 行動開發 (7) – Sencha Touch Device Profile 實作教學
- Web App 行動開發 (8) – Sencha Touch Component 介紹