Dijit、ExtJS、jQuery UI 異同淺析

簡介: 當今,各種 JavaScript 框架在前端開發中已經至關普及。Dojo、Ext jQuery 等主流 JavaScript 框架不只提供了一系列核心 API 來屏蔽瀏覽器差別,簡化 DOM 操做、加強 JavaScript 原生 API,還爲用戶提供了豐富的 UI 控件庫來幫助進行敏捷 Web 開發。然而因爲各個框架的核心 API 的設計思路不盡相同,其 UI 控件的架構設計也各具特點。您是否想要建立一個本身的 UI 控件,但面對這些框架,而不知如何取捨?您是否長期從事某一種框架的開發,沒有時間研究別的框架,卻又想了解一樣的問題別人是怎麼解決的?本文將爲您介紹 Dijit、Extjs、jQuery UI 從使用方式到架構實現的異同,展示不一樣的設計思路,但願能爲您帶來幫助。javascript

發佈日期: 2012 年 2 月 16 日
級別: 中級
訪問狀況 : 1206 次瀏覽
評論: 1 (查看 | 添加評論 - 登陸) html

平均分 5 星 共 15 個評分 平均分 (15個評分)
爲本文評分

Dijit、ExtJS、jQuery UI 簡介前端

Dojo 是開源 JavaScript 庫中起步較早的先行者之一。由 Alex Russell, David Schontzler, Dylan Schiemann 等人於 2004 年創立。Dojo 具備相似 Java 的包機制 (packaging system), 將 JS 代碼根據功能進行了模塊化。主要包含 Dojo、Dijit 以及 Dojox 三個包。其中 Dojo 包提供穩定的內核 API,Dijit 包提供各種 UI 控件,Dojox 包則囊括了一系列實驗性的 API 及控件(其中不乏一些獲得長期維護、穩定性已至關高的包,如 dojox.charting 包和 dojox.grid 包等)。在 Dojo 1.7 版本中,Dijit 包的內部結構被進行了更細的模塊拆分和重構,但因爲撰寫本文時其還沒有發佈,本文中的 Dijit 相關內容仍將基於 Dojo 1.6.1 版本。java

ExtJS 是當今一套被普遍應用於前端開發的 Ajax 以及控件框架,其歷史能夠追溯到 Yahoo! User Interface。在 Jack Slocum 的耕耘下,ExtJS 逐漸成長。自從 ExtJS 的 2.0 版本發佈後,其使用範圍逐漸擴展到世界各地。3.X 版本中推出了更多易用的控件,ExtJS 的優點在於其強大的控件庫,以及其對於各類前臺功能的封裝,造成了完整的一套面向對象風格的 JS 控件庫。隨着和 Sencha 合併,ExtJS 也向觸摸屏發展,不過其 Ext JS 庫的發展從未中止。現在的 ExtJS 4.0 提供了更完整的 JavaScript API 庫,減小對 Array、Function、String 等底層類的重寫,大大的減小了不一樣的 JS 庫之間的衝突問題。因爲 4.0 版本不向下兼容,對升級形成了必定的影響,筆者尚未機會去深刻使用 ExtJS 4.0 版本,所以本文着重介紹的是 ExtJS 3.X 版本。node

jQuery UI 是 jQuery 的官方 UI 控件庫。jQuery 的大名在業內可謂是無人不知無人不曉。自 2006 年發佈了其第一個穩定版以後,其輕量、易用的特色使其深刻人心。jQuery UI 於 2007 年發佈,它徹底基於 jQuery 提供的插件機制,提供了底層交互及動畫功能以及一些可定製樣式的 UI 控件。雖然提供的控件數量很少,但它們都具有了 jQuery 小巧的特色,其使用風格也與 jQuery 核心 API 一致。撰寫本文時,其最新的穩定版本爲 1.8.16,本文中關於 jQuery UI 的內容都基於該版本。jquery

回頁首web

控件的使用方式編程

在討論各個控件庫的架構實現以前,首先讓咱們從用戶的角度來看看 Dijit、ExtJS、jQuery UI 控件的的使用方式,對它們有一個直觀的瞭解。api

控件的使用無外乎建立控件、操做控件,而在建立控件以前,咱們每每須要先加載控件資源。下面就讓咱們從控件資源加載開始聊起(控件 CSS 文件編寫與加載不在本文範圍內)。數組

控件資源加載

Dijit 篇:藉助於 Dojo 提供的自動加載模塊依賴項的機制,加載 Dijit 控件所需資源很是簡單,用戶並不須要瞭解一個控件究竟須要哪些 JS 文件的支持,只需向頁面添加 Dojo 核心文件 dojo.js 並使用 dojo.require 函數導入控件對應模塊便可。


清單 1. Dijit 資源加載
<script type="text/javascript" src="lib/dojo/dojo.js"></script> 
 <script type="text/javascript"> 
    dojo.require("dijit.form.Button"); 
 </script>

上述代碼將自動加載 Button 控件所依賴的全部 JS 文件。

ExtJS 篇:ExtJS 自己沒有一套完整的自動加載依賴資源的機制,在大多數狀況下,用戶都是使用完整的 ExtJS 庫,只須要導入 /ExtJS/adapter/ext/ext-base.js 和 /ExtJS/ext-all.js 這 2 個文件便可。通常狀況下爲了方便調試,會使用 ext-base-bug.js 和 ext-all-debug.js 這 2 個文件。


清單 2. ExtJS 資源加載
<script type="text/javascript" src="JsLib/ExtJS/adapter/ext/ext-base-debug.js"></script> 
 <script type="text/javascript" src="JsLib/ExtJS/ext-all-debug.js"></script>

固然爲了節省資源也能夠只加載部分的 ExtJS 資源庫,ExtJS 提供了一個名爲 ext.jsb2 的文件(該文件描述了各個 JS 文件之間的依賴狀況), 讓用戶查詢各個文件之間的依賴狀況,方便用戶進行 ExtJS 控件的單個導入。

jQuery UI 篇:因爲 jQuery 也沒有提供一套完整的自動加載依賴資源的機制,所以用戶須要手動將所使用控件的依賴資源逐一導入頁面。以 jQuery UI 中的 button 控件爲例,須要經過手動爲頁面添加以下代碼導入所需 js 文件。


清單 3. jQuery UI 資源加載
<!-- 導入 jquery core --> 
 <script type="text/javascript" src="lib/jquery/jquery-1.6.2.js"></script> 
 <!-- 導入 Button 控件所依賴的 JS 資源 --> 
 <script type="text/javascript" src="lib/jquery/ui/jquery.ui.core.js"></script> 
 <script type="text/javascript" src="lib/jquery/ui/jquery.ui.widget.js"></script> 
 <script type="text/javascript" src="lib/jquery/ui/jquery.ui.button.js"></script>

這樣手動加載資源的方式須要用戶清楚瞭解一個控件依賴哪些資源項,這在使用某些依賴項較多的控件,如 dialog 時會帶來困擾。雖然在最終發佈的產品中,每每會將頁面中全部使用到的 JS 代碼進行合併壓縮,用戶僅須要在頁面中導入合併壓縮過的 JS 文件便可,但用戶仍須要在瞭解頁面須要哪些資源以後再對其進行合併壓縮(固然用戶也能夠選擇一次性將全部 jQuery UI 的代碼合併壓縮到一個文件中)。

控件建立

Dijit 篇:Dijit 控件的建立方式有兩種,編程式(programmatic)以及聲明式(declarative)。

使用編程方式使用 Dijit 控件與使用傳統面嚮對象語言進行 UI 編程很是相似。一般只須要提供一個 DOM 節點、一個散列參數表並使用 new 關鍵字建立一個所需的 dijit 控件對象便可。


清單 4. Dijit 使用 new 關鍵字建立 Button 控件
<html> 
 <head> 
 <script type="text/javascript"> 
   dojo.addOnLoad(function(){ 
      var button = new dijit.form.Button({ 
         id: "programmatic", 
         label: "This is a button"
      }, "buttonNode"); 
   }); 
 </script> 
 </head> 
 <body> 
   <div> 
      <button id="buttonNode"> 
      </button> 
   </div> 
 </body> 
 </html>

上述代碼將會建立一個 Button 控件,並將 id 爲 buttonNode 的 button 標籤元素替換爲實例化的控件的 DOM 樹。而 button 變量則指向該控件實例的引用。此外還能夠先建立控件實例,再將其插入到頁面的 DOM 樹中。


清單 5. Dijit 使用 new 關鍵字建立 Button 控件
<html> 
 <head> 
 <script type="text/javascript"> 
   dojo.addOnLoad(function(){ 
      var button = new dijit.form.Button({ 
         id: "programmatic", 
         label: "This is a button"
      }); 
      button.placeAt("buttonContainer"); 
   }); 
 </script> 
 </head> 
 <body> 
   <div> 
      <p id="buttonContainer"> 
      </p> 
   </div> 
 </body> 
 </html>

上述代碼會建立一個 Button 控件實例並將其 DOM 樹其插入到 id 爲 buttonContainer 的 p 標籤元素之下。

使用聲明方式使用 Dijit 控件時,須要爲 HTML 標籤添加 data-dojo-type 以及 data-dojo-props 屬性,其中 data-dojo-type 表示所要生成控件的名稱,data-dojo-props 包含生成控件所需的構造參數。使用此種方法建立 Dijit 控件時,能夠在導入 Dojo 核心文件時經過 parseOnLoad 屬性配置是否自動實例化頁面上全部控件。


清單 6. 開啓 parseOnLoad 屬性,自動建立控件
<html> 
 <head> 
 <script type="text/javascript" src="lib/dojo/dojo.js" data-dojo-config="parseOnLoad: 
 true"/> 
 </head> 
 <body> 
   <button data-dojo-type="dijit.form.Button" 
   data-dojo-props= 'id: "declarative"; label: "This is a button"’ /> 
 </body> 
 </html>

上述代碼將會在頁面加載完畢後自動實例化一個 Button 控件。當用戶選擇關閉 parseOnLoad 選項時,能夠經過手動方式實例化所須要的控件。


清單 7. 關閉 parseOnLoad 屬性,手動建立控件
<html> 
 <head> 
 <script type="text/javascript" src="lib/dojo/dojo.js" data-dojo-config="parseOnLoad: 
 false"/> 
 <script type="text/javascript"> 
   dojo.addOnLoad(function(){ 
      dojo.parser.parse(); 
   }); 
 </script> 
 </head> 
 <body> 
   <button data-dojo-type="dijit.form.Button" 
   data-dojo-props= 'id: "declarative"; label: "This is a button"’ /> 
 </body> 
 </html>

不管是否啓用 parseOnLoad 選項,其本質都是使用 dojo.parser 這個對象對頁面進行掃描,解析 HTML 標籤中的屬性,並根據這些屬性內容實例化控件。須要注意的是,dojo.parser 並非 Dojo base 的一部分,也就是說以前導入的 Dojo 核心文件 dojo.js 並不包含 dojo.parser 模塊,所以一般狀況下使用 dojo.parser 的話須要額外添加代碼導入 dojo.parser 模塊。


清單 8. 導入 dojo.parser 模塊
dojo.require("dojo.parser");

然而在使用 Button 控件時,因爲咱們已經導入了 dijit.form.Button 模塊 ,Dojo 會爲咱們自動加載全部該模塊依賴的資源,其中就包括 dojo.parser 模塊對應的 JS 文件。

ExtJS 篇:ExtJS 控件的生成方式基於使用 new 關鍵字建立一個 ExtJS 控件對象,將其放入須要渲染的 DOM 節點中。例如:


清單 9. ExtJS 使用 new 關鍵字建立 Button 控件
<script type="text/javascript"> 
   var button = new Ext.Button({ 
      id: 'button', 
      text: 'button', 
      renderTo: Ext.getBody() 
   }); 
 </script>

上述代碼中,經過賦予 renderTo 參數一個頁面的 DOM 節點,將一個 Ext.Button 對象添加到 body 元素中,至此一個簡單的 ExtJS 控件完成了。

此外 ,ExtJS 還容許用戶經過 add() 和 doLayout() 方法,向容器控件(繼承自 Ext.Container 類的控件)中添加缺省 renderTo 屬性的子控件。


清單 10. ExtJS 向容器控件添加子控件
<script type="text/javascript"> 
   var mainPanel = new Ext.Panel({ 
      renderTo: Ext.getBody() 
   }); 
   var button = new Ext.Button({ 
      id:'button'
   }); 
   mainPanel.add(button); 
   mainPanel.doLayout(); 
 </script>

上述代碼首先將 mainPanel 對象渲染到 body 元素中,以後經過 add() 和 doLayout() 方法將缺省 renderTo 屬性的 button 對象添加到 mainPanel 對象中,並從新渲染 mainPanel 對象,此時 button 對象也會被渲染到頁面上。

ExtJS 沒有像 Dijit 那樣提供經過解析 HTML 標籤的方式建立控件,可是提供了簡單的反射機制,使用戶可經過描述符來生成控件。


清單 11. ExtJS 經過描述符生成控件
<script type="text/javascript"> 
   var mainPanel = new Ext.Panel({ 
      items: [{ 
         xtype: 'button', 
         id: 'button'
      }], 
      renderTo: Ext.getBody() 
   }); 
 </script>

上述代碼首先實例化一個 ExtJS 的 Panel 控件並在其 items 屬性中添加了一段關於 button 控件的描述符,在 mainPanel 對象渲染的過程當中,會遍歷 items 數組中的每個對象,若是對象沒有被實例化,則會尋找描述符對象中的 xtype 屬性。然後,在控件建立的過程當中,Ext.ComponentMgr 的 create() 方法會根據描述的 xtype 屬性尋找在 Ext.reg 中註冊過的控件類,經過反射的方式建立一個對應的控件實例。

jQuery UI 篇:jQuery UI 控件的使用方式秉承了 jQuery 一向簡潔的風格。它並不提供相似於 dojo.parser 這樣的工具類來掃描頁面並根據 HTML 標籤自動生成控件實例,也不像傳統的面嚮對象語言那樣使用 new 關鍵字來建立控件,而是經過 jQuery 插件經常使用的鏈式書寫方式來建立控件。


清單 12. jQuery UI 建立 Button 控件
<html> 
 <head> 
 <script type="text/javascript"> 
   $(function){ 
      $("#buttonNode").button({ 
         label: "button"
      }); 
   }); 
 </script> 
 </head> 
 <body> 
   <div> 
      <button id="buttonNode"></button> 
   </div> 
 </body> 
 </html>

上述代碼首先使用 jQuery 核心方法 $() 獲取頁面中全部符合查詢條件的 HTML DOM 節點(本例中只有一個 id 爲 buttonNode 的 DOM 節點符合條件),並將返回的 DOM 節點數組包裝成一個 jQuery 對象。以後調用 $.ui.button 插件爲 jQuery 對象提供的 button 方法根據傳入的散列參數表爲這些節點建立 $.ui.button 控件。

控件操做

Dijit 篇:在建立 Dijit 控件以後,用戶能夠經過 dijit.byId、dijit.findWidgets、dijit.byNode、dijit.getEnclosingWidget 等方法獲取控件實例。


清單 13. 獲取 Dijit 控件對象
// 獲取 widget id 爲 programmatic 的控件
 var widget = dijit.byId("programmatic"); 
 // 獲取 body 標籤下的全部控件
 var widgets = dijit.findWidgets(dojo.body()); 
 // 獲取 DOM 樹根節點爲以 node 的控件
 var widget = dijit.byNode(node); 
 // 獲取 DOM 樹中包含 node 節點的控件
 var widget = dijit.getEnclosingWidget(node);

獲取控件實例以後能夠像使用 Java 類那樣調用控件方法。並使用 get、set 方法來獲取/設置控件的屬性。


清單 14. Dijit 控件屬性獲取/設置及方法調用
// 調用控件方法
 widget.someMethod(); 
 // 使用 get 獲取控件屬性
 var value = widget.get(attributeName); 
 // 使用 set 設置控件屬性
 widget.set(attributeName, value);

ExtJS 篇:ExtJS 並無像 Dijit 同樣提供了經過 DOM 節點查找控件的方法,而是隻提供經過控件 id 號獲取控件對象的方法。


清單 15. 獲取 ExtJS 控件對象
// 獲取控件對象
 var button = Ext.getCmp("button");

Ext.getCmp 方法返回的就是一個完整的 ExtJS 的控件對象,包含了控件對象的全部變量及方法。與 Dijit 不一樣的是,ExtJS 的成員變量大可能是經過使用變量名去獲取/設置的,僅對部分屬性提供了對應的 get/set 方法(其緣由和內容將在後文的「屬性獲取/配置方法」章節中具體闡述), 而 ExtJS 的控件方法調用仍是與 Java 的方法調用相似的。


清單 16. ExtJS 控件屬性屬性獲取 / 設置及方法調用
// 獲取控件成員變量,直接訪問成員變量
 var buttonText = button.text; 
 //button 控件爲 width 屬性添加了 setWidth 方法,以便 width 屬性改變後,對 DOM 節點進行處理。
 button.setWidth(100); 
 // 調用控件成員函數,相似 Java 的對象方法調用方式
 button.focus();

jQuery UI 篇:操做 jQuery UI 控件的方式與建立 jQuery UI 控件的方式很是類似。


清單 17. jQuery UI 控件方法調用
<html> 
 <head> 
 <script type="text/javascript"> 
   $(function){ 
      // 爲 button 標籤建立 Button 控件
      $("#buttonNode").button({ 
         label: "button"
      });
      // 調用 Button 控件的 disable 方法
      $("#buttonNode").button("disable"); 
   }); 
 </script> 
 </head> 
 <body> 
   <div> 
      <button id="buttonNode"></button> 
   </div> 
 </body> 
 </html>

上述代碼中,前後調用了兩次 button 方法,但其含義徹底不一樣。第一次調用 button 方法時,傳入了一個散列參數表,爲對應節點建立了 $.ui.button 控件。第二次調用 button 方法時,參數爲一個字符串,此時 jQuery 調用了與字符串同名的控件成員方法。

jQuery UI 並無爲控件屬性提供默認的 get/set 方法,但用戶能夠經過以下方式獲取/設置控件屬性:


清單 18. jQuery UI 控件屬性獲取 / 設置
// 調用 option 方法獲取 $.ui.button 控件的屬性表
 var options = $("#buttonNode").button("option"); 
 // 設置 buttonNode 節點對應的 $.ui.button 控件的 label 屬性
 $("#buttonNode").button({ 
   label: "changed label"
 });

上述代碼中第二次調用 button 方法時雖然傳入的是一個散列參數表,但因爲以前已經爲 id 號爲 buttonNode 的 DOM 節點建立過 $.ui.button 控件,所以不會再次建立控件對象,而是取出已建立的控件對象,並設置其對應的屬性。

回頁首

控件的架構實現

在瞭解了 Dijit、ExtJS、jQuery UI 的使用方式以後,讓咱們從開發者的角度再來看看這些控件背後的架構與實現機制。

控件聲明

Dijit 篇:經過前面的使用範例能夠看到 Dijit 控件是面向對象的。每一個 Dijit 控件都是由 dojo.declare 方法聲明的一個類。下面是 dijit.form.Button 控件的聲明代碼片斷


清單 19. 聲明 dijit.form.Button 類
dojo.declare( 
   // 控件名
   "dijit.form.Button", 
   // 基類
   dijit.form._FormWidget, { 
   // 成員屬性
   baseClass: "dijitButton", 
   templateString: dojo.cache("dijit.form", "templates/Button.html"), 
   ... 
   // 成員方法
   buildRendering: function(){...}, 
   reset: function(){...}, 
   _onClick: function(/*Event*/ e){...} 
   ... 
 });

上述代碼聲明瞭一個 dijit.form.Button 類,在建立該控件對象時,只需經過 new 關鍵字進行實例化便可。

若是您瞭解 dojo.declare 用法(具體用法參見 http://dojotoolkit.org/reference-guide/dojo/declare.html#dojo-declare)的話能夠看出,控件類的聲明方式與聲明一個普通的 Dojo 類並無什麼區別。而一個 Dijit 控件類與普通 Dojo 類的主要區別在於 Dijit 控件類繼承了一些特定的基類。在後文中將會爲您介紹那些在背後支撐 Dijit 的基類。

ExtJS 篇: ExtJS 經過使用 Ext.extend 方法來實現繼承,並聲明一個新的控件類。以 Ext.Button 爲例:


清單 20. 聲明 Ext.Button 類
// 其中 Ext.BoxComponent 是一個繼承自 Ext.Component 的類,
 Ext.Button = Ext.extend( 
   // 基類
   Ext.BoxComponent, { 
   // 控件屬性
   hidden: false, 
   … . 
   // 成員方法
   initComponent: function(){ 
      Ext.Button.superClass.iniiComponent.call(this); 
      ... 
   } 
 });

上述代碼聲明瞭一個基於 Ext.BoxComponent 的 Ext.Button 控件類。

在每個控件類的聲明結束後,還能夠經過 Ext.reg 方法來爲該控件類進行全局的註冊,只有通過註冊的控件類,才能經過描述符由以前提到的反射機制來生成:


清單 21. 註冊 Ext.Button 類
Ext.reg('button',Ext.Button);

Ext.extend 方法除了提供繼承的機制來聲明新的控件,還能對已有的控件進行擴展,以 Ext.Component 爲例:


清單 22. 擴展 Ext.Component 類
Ext.extend(Ext.Component, Ext.util.Observable, { 
   // 新的成員變量以及方法
 });

上述代碼將 Ext.util.Observable 類的成員變量以及方法擴展到 Ext.Component 類中,而且將第三個參數對象中的成員變量及方法寫入 Ext.Component 類中,這些屬性及方法將覆蓋 Ext.Component 類中的同名屬性及方法。

jQuery UI 篇: jQuery UI 在 jquery.ui.widget.js 文件中提供了 $.widget 這個工廠方法來聲明 jQuery UI 控件類。下面是 $.ui.button 控件的聲明代碼片斷:


清單 23. 聲明 $.ui.button 類
$.widget("ui.button", { 
   options: { 
      // 成員屬性
      disabled: null, 
      ... 
   }, 
   
   // 聲明體內只聲明成員方法
   _create: function{ 
      ... 
   }, 
   refresh: function{ 
      ... 
   }, 
   ... 
 });

其書寫方法與聲明一個 Dijit/ExtJS 控件類較爲相似,但須要注意的是,jQuery UI 控件的屬性須要寫在一個 options 對象中,而不是直接寫在對象的聲明體內。此外咱們並無像聲明 Dijit/ExtJS 控件類那樣爲其指定一個父類,其緣由將在下一節繼承體系中說明。

既然 jQuery UI 控件的聲明方法和 Dijit/ExtJS 控件如此相似,爲何它們的建立方法又如此不一樣呢?咱們來看一下上面的代碼片斷背後到底作了哪些工做。

在調用 $.widget 方法時首先會聲明一個名爲 $.ui.button 類,理論上說,有了這個類,用戶就能夠經過 new 關鍵字來建立該控件類的實例了。


清單 24. 使用 new 方法建立 $.ui.button 控件
<html> 
 <head> 
 <script type="text/javascript"> 
   $(function){ 
      // 使用 new 關鍵字建立一個 label 爲 button 的 $.ui.button 控件對象
      new $.ui.button({label: "button"}, $("#buttonNode")); 
   }); 
 </script> 
 </head> 
 <body> 
   <div> 
      <button id="buttonNode"></button> 
   </div> 
 </body> 
 </html>

這樣的書寫方法和使用 Dijit 以及 ExtJS 的基本一致,可是爲了保持和 jQuery 核心 API 同樣的鏈式書寫方式,在聲明完這個控件類以後 ,$.widget 方法還會調用 $.widget.bridge 方法來向 $.fn(jQuery 對象的 prototype) 添加一個與控件名同名的插件方法,在本例中爲 button。該方法在被調用時會根據傳入的參數來決定是調用控件的方法,仍是建立控件對象,或是取出已建立的控件對象來修改屬性。


圖 1. $.fn.button 方法運行流程圖
圖 1. $.fn.button 方法運行流程圖

私有方法

在上一節的 Dijit 及 jQuery UI 相關代碼實例中,您可能發現了一些名字如下劃線"_"開頭的方法,這些方法都是私有方法,但在不一樣的 UI 庫中又略有不一樣。

Dijit 篇:Dojo 的代碼風格規定如下劃線"_"開頭的方法爲對象私有,然而這僅僅是代碼風格上的要求,用戶在使用時仍可直接調用 Dijit 控件的這些方法(但不推薦)。

ExtJS 篇: ExtJS 的控件類中,沒有直接用來區分私有和公共方法的風格規範,而是經過方法前添加規範的註釋進行劃分,其形式相似於 Java 中的方法註釋。帶有註釋的成員方法,會被 Ext API 文檔收錄。經過這種方式,使 API 文檔只展現公共變量和方法,以保證普通用戶在查詢 API 文檔時,僅能接觸到 ExtJS 提供的公共方法。當須要擴展一個 ExtJS 的控件,對其添加成員變量及方法時,用戶可按照 ExtJS 的文檔規範進行註釋,將須要開放的公共方法收錄到 ExtJS 的 API 文檔中,有助於他人查詢和使用自助開發的控件類。


清單 25. ExtJS 公共方法的代碼註釋
/** 
 * Adds a CSS class to the component's underlying element. 
 * @param {string} cls The CSS class name to add 
 * @return {Ext.Component} this 
 */ 
 addClass : function(cls){ 
   // 具體實現略
 }

jQuery UI 篇: jQuery UI 與 Dojo 倡導的代碼風格同樣,規定如下劃線"_"開頭的方法爲控件的私有方法。但與 Dojo 不一樣的是 ,jQuery UI 不容許這些內部方法被用戶調用,也就是說下例中調用 Button 控件 _create 方法的代碼是無效的。


清單 26. 調用 jQuery UI 控件私有方法
<html> 
 <head> 
 <script type="text/javascript"> 
   $(function){ 
      // 爲 button 標籤建立 Button 控件
      $("button").button({ 
         label: "button"
      });
      // 這行代碼不會執行控件的任何方法
      $("button").button("_create"); 
   }); 
 </script> 
 </head> 
 <body> 
   <div> 
      <button></button> 
   </div> 
 </body> 
 </html>

繼承體系

Dijit 篇:得益於 Dojo 提供的類繼承機制,咱們能夠經過繼承一些特定的基類,方便地擴展出一個標準的 Dijit 控件。這些基類抽象了一個 Dijit 控件所需的底層功能,大大方便了新控件的開發。

須要注意的是,Dojo 的繼承機制與 Java 有所不一樣,它支持多重繼承,但類的 prototype 只有一個(Dojo 繼承機制的具體實現已超出本文範圍,請參閱 Dojo 類機制實現原理分析), 所以一個 Dijit 控件每每也繼承了多個父類。如 dijit.form.Select 控件:


清單 27. 聲明 dijit.form.Select 控件
dojo.declare( 
   // 控件名
   "dijit.form.Select", 
   // 父類
   [dijit.form._FormSelectWidget, dijit._HasDropDown], { 
   // 成員屬性
   baseClass: "dijitButton", 
   templateString: dojo.cache("dijit.form", "templates/Button.html"), 
   ... 
   // 成員方法
   buildRendering: function(){...}, 
   reset: function(){...}, 
   _onClick: function(/*Event*/ e){...} 
   ... 
 })

一個 Dijit 控件背後通常都須要由如下基類來支撐:dijit._WidgetBase,dijit._Templated,dijit._CssStateMixin 等。


圖 2. Dijit 控件繼承體系圖
圖 2. Dijit 控件繼承體系圖

其中最底層的基類是 dijit._WidgetBase。全部 Dijit 包中的控件都繼承於它。在這個類中定義了一個標準 Dijit 控件的基本建立流程。

大多數 Dijit 控件在繼承 dijit_WidgetBase 這個基類以外還繼承了 dijit._Templated。故名思意該基類爲控件提供了模板功能,在後文中將詳細介紹模板功能。

dijit._CssStateMixin 也是大部分 Dijit 所繼承的基類。它會監聽控件的一系列屬性,如"disabled","readOnly","checked","selected","focused", "state","hovering","active"等。當這些屬性發生改變時,控件 DOM 節點上的某些 CSS class 名會被同步修改,從而反映出控件的當前狀態。

此外,該類還在控件建立的過程當中爲其鼠標事件(mouseenter, mouseleave, mousedown)綁定了回調函數。該回調函數監聽用戶的鼠標操做,並及時更新控件的"hovering"、"active"屬性,最終對其 DOM 節點上的 CSS class 進行修正。

ExtJS 篇: ExtJS 有着相似於 Dijit 的繼承機制,也支持經過 Ext.extend 方法擴展控件的功能(Ext.extend 使用方法已在前文展現,這裏再也不重複)。

ExtJS 全部的控件類都繼承自 Ext.Component 類,這個類定義了 ExtJS 控件的基本建立流程,涵蓋了事件定製、模板定製、DOM 節點渲染等方法。所以,普通的 ExtJS 控件僅需繼承 Ext.Component 類便能知足全部的功能需求。而有特殊功能須要的控件,經過擴展諸如 Ext.dd.DragDrop,Ext.ClickRepeater 等類來實現拖拽,鼠標事件處理等功能。


圖 3. ExtJS 控件繼承體系圖
圖 3. ExtJS 控件繼承體系圖

除了經過繼承的方式,ExtJS 還提供了 Ext.Override 方法供用戶重寫已聲明過的對象,後文的「DOM 樹構建」章節中會描述其部分做用。

jQuery UI 篇: jQuery core 並無提供相似 Dojo/ExtJS 的繼承機制,可是在 jQuery UI 1.8 中,爲了方便擴展控件,開始爲控件類引入了相似的繼承機制。用戶能夠通 $.widget 工廠方法在聲明控件的同時爲其指定父類 ,


清單 28. 聲明 $.ui.myButton 控件
$.widget('ui.myButton', 
   // 父類
   $.ui.button,{ 
   // 定製代碼
   ... 
 });

然而 jQuery UI 的這套繼承機制並不支持多重繼承,一個控件類只能基於一個父類。當其父類缺省時(上例中的第二個參數),jQuery UI 會自動將 $.Widget 類做爲控件的父類。

jQuery UI 的繼承體系相對簡單,通常來講,僅有 $.Widget 這個基類是必須的。$.Widget 類定義了 jQuery UI 控件的基本建立流程及屬性配置(.option)、控件銷燬 (.destroy) 等基本方法。

此外 ,jQuery UI 還提供了一些可選的基類,如用來監聽鼠標行爲的 $.ui.mouse, 用來支持控件拖拽的 $.ui.draggable,$.ui.droppable 等。


圖 4. jQuery UI 控件繼承體系圖
圖 4. jQuery UI 控件繼承體系圖

實例化流程

Dijit 篇:前面提到 dijit._WidgetBase 類中定義了一個 Dijit 控件的基本建立流程,在實例化一個控件時會自動執行如下步驟:


圖 5. Dijit 控件基本建立流程
圖 5. Dijit 控件基本建立流程

其中 dijit._WidgetBase 會爲控件暴露出如下三個方法以供覆寫。

postMixinProperties:此時控件對象生成完畢,但還未向全局進行註冊,且 DOM 節點還未構建。能夠經過覆寫此方法爲控件初始化一些額外的資源。

buildRendering:控件須要實現此方法來爲已註冊好的控件對象構建 DOM 樹。

postCreate:此時控件已經建立完畢,且已繪製到頁面中。能夠經過覆寫此方法來對控件節點進行最後的調整,如綁定事件的回調函數等。

ExtJS 篇: Ext.Component 類做爲全部 ExtJS 控件的基類,定義了 ExtJS 控件最基本的建立流程:


圖 6. ExtJS 控件基本建立流程
圖 6. ExtJS 控件基本建立流程

其中,提供了 initComponent、onRender、afterrender、initEvents 方法以供子控件重寫。其中 onRender 方法是 render 方法中繪製控件默認 DOM 樹的環節。

initComponent:經過重寫此方法來爲控件添加新的初始化參數,添加須要註冊的基本事件,並提供接口,容許使用者對該控件基本事件添加的監聽器。

onRender:控件須要覆寫此方法來提供其對應的 DOM 樹模板。

afterrender:此時控件已經建立完畢,並渲染到頁面上,能夠在此方法中對控件對象進行初始化狀態的調整。

initEvents:經過重寫此方法來註冊用戶添加的自定義事件,併爲自定義事件添加對應的監聽器。

jQuery UI 篇: jQuery UI 控件的建立流程由 $.Widget 定義。具體步驟以下:


圖 7. jQuery UI 控件基本建立流程
圖 7. jQuery UI 控件基本建立流程

其中,控件須要覆寫 _create 方法以及 _init 方法。

_create:覆寫此方法爲控件在頁面上構建其 DOM 樹,併爲節點綁定事件的回調函數等。

_init:覆寫此方法來根據控件屬性對建立完的控件 DOM 樹內容進行調整。在控件實例化以後,每當屬性發生修改時,該方法也會被調用。

DOM 樹構建

Dijit 篇:上一節中提到,一個 Dijit 控件須要實現 buildRendering 方法來構建控件的 DOM 樹。而 dijit._Templated 這個基類爲 Dijit 控件提供了 buildRendering 方法的基本實現(基於 dijit._Templated 的控件能夠根據須要爲 buildRendering 方法追加代碼,詳細方法請參閱 inherite), 使得控件能夠在該階段經過加載模來構建本身的 DOM 樹。繼承了 dijit._Templated 的 Dijit 控件類都會有一個名爲 templateString 的屬性,該屬性顧名思義,是用來構建該控件的 HTML 模板字符串。在執行 buildRendering 方法時,該字符串會被自動用來替換頁面上的節點,從而構建出控件的 DOM 樹。


清單 29. 控件中的 templateString
dojo.declare("myWidget", [dijit._Widget, dijit._Templated],{ 
   ... 
   // 模板字符串
   templateString: "<div>This is a simple widget</div>", 
   ... 
 });

爲了使控件代碼和模板內容能夠獨立開,每個控件的 templateString 內容每每並不直接寫在 JS 文件中,而是保存在一個對應的 HTML 模板文件中。Dijit 包中某個控件對應的模板文件被放在該控件所在包下的 templates 文件夾中,如 dijit.form.Button 控件的模板文件爲 dijit/form/templates/Button.html。在使用模板時,只需經過 dojo.cache 方法將其加載並賦給 templateString 便可。


清單 30. 使用 dojo.cache 加載模板
dojo.declare("dijit.form.Button", dijit.form._FormWidget,{ 
   ... 
   // 模板內容
   templateString: dojo.cache("dijit.form", "templates/Button.html"), 
   ... 
 });

Dijit 控件的模板中不只僅定義了控件的 DOM 樹結構,還容許使用表達式將控件屬性直接插入 DOM 樹中。如,使用如下模板片斷的控件在生成時,會將 id 爲 labelNode 的 span 節點的內容替換成控件的 label 屬性。


清單 31. 在 Dijit 控件模板中使用 ${} 插入控件屬性
<div> 
   <span id="labelNode">${label}</span> 
 </div>

此外 ,Dijit 模板還容許使用 dojoAttachNode 屬性來爲模板中的指定節點設置訪問假名。以下面模板片斷中 dojoAttachNode 屬性爲 labelNode 的 span 節點,能夠經過控件的 labelNode 屬性來獲取。


清單 32. 在 Dijit 控件模板中使用 dojoAttachNode 屬性
<div> 
   <span dojoAttachNode="labelNode"></span> 
 </div> 

 //widget 爲使用上述模板的控件對象
 var spanNode = widget.labelNode

相似的,對模板中的節點添加 dojoAttachEvent 屬性能夠爲其綁定事件回調函數。以下面模板片斷中的 dojoAttachEvent 屬性爲所在的 span 節點的 click 事件綁定了控件的 _onClick 方法做爲回調函數。


清單 33. 在 Dijit 控件模板中使用 dojoAttachEvent 屬性
<span> 
   <span dojoAttachEvent="onclick: _onClick"></span> 
 </span>

上文的「實例化流程」章節中提到,ExtJS 容許用戶在擴展控件類的過程當中,經過重寫 onRender 方法爲控件的構建其 DOM 樹,也在 render 方法中提供了對自定義模板繪製控件 DOM 樹的支持。

ExtJS 篇:在 ExtJS 的控件設計中,老是將一整個控件拆分爲多個小模塊,例如 header,toolbar,body,bottombar,footer 等,所以繪製控件 DOM 樹時,須要在 onRender 方法中分別繪製控件對象的各個部分,並支持經過配置對象的成員變量,例如 headerCssClass,headerCfg,headerStyle 等,對控件小模塊的樣式進行微調,增長控件樣式的靈活性。

以下代碼以 Ext.Panel 爲例,展現其 onRender 方法的部分實現:


清單 34. ExtJS 中 Ext.Panel 類 onRender 方法的部分實現
onRender : function(ct, position){ 
   ... 
   // 其餘內容略,僅展現部分 DOM 樹繪製代碼,這裏的 dom 爲省略代碼中獲取的一個頁面 DOM 節點
   this.createElement('header', dom); 
   this.createElement('body', dom); 
   this.createElement('footer', dom); 
 }, 
 createElement: function(name, pnode){ 
   ... 
   // 其餘內容略,僅展現根據配置項,改變控件對象的部分 DOM 樹的 CSS 樣式
   if(this[name+'Cfg']){ 
      this[name] = Ext.fly(pnode).createChild(this[name+'Cfg']); 
   }else{ 
      var el = document.createElement('div'); 
      el.className = this[name+'Cls']; 
      this[name] = Ext.get(pnode.appendChild(el)); 
   } 
   if(this[name+'CssClass']){ 
      this[name].addClass(this[name+'CssClass']); 
   } 
   if(this[name+'Style']){ 
      this[name].applyStyles(this[name+'Style']); 
   } 
 }

以上是從擴展控件的角度,經過重寫控件 DOM 樹模板來繪製新的控件頁面元素。接着讓咱們來了解下 ExtJS 控件在使用過程當中可配置的自定義模板。在建立一個新控件對象時,能夠經過配置 tpl,tplWriteMode 以及 data 三個屬性來定製控件的 DOM 樹。tpl 接收的能夠是一個 HTML 模板的 string 對象,也能夠是一個基於 string 對象建立的 Ext.XTemplate 對象。在控件的 render 方法中,根據配置不一樣的 tplWriteMode 屬性 ,ExtJS 會選擇不一樣的覆蓋模式(前置添加 insertBefore, 後置添加 insertAfter, 總體覆蓋 overWrite 等), 最後將 data 參數中的數據填充到 Ext.XTemplate 模板類對象中。

在控件使用的過程當中,若是是單次使用自定義摸版,能夠直接在創建對象是使用新的模板類:


清單 35. 在建立 ExtJS 控件時,使用模板類的方法
var textField = new Ext.form.TextField({ 
   tpl: '<div><label>{defaultLabel}</label></div>', 
   tplWriteMode:'insertBefore', 
   data: { 
      defaultLabel: 'Default'
   } 
 });

若是須要對某種控件的全部的模板都進行修改,可使用前文提到過的 Ext.Override 方法重寫對應的控件對象:


清單 36. 重寫 ExtJS 控件類的模板類相關屬性
Ext.Override(Ext.form.TextField, { 
   tpl: '<div><label>{defaultLabel}</label></div>',
   tplWriteMode: 'insertBefore',
   data: {
      defaultLabel: 'Default'
   } 
 });

值得一提的是 ,Ext.XTemplate 類是一個實用性很強的模板類,經過其 tpl 標籤配合 for,if 等操做符可繪製出能接受複雜 JSON 對象的模板類,並能在模板類中添加內部方法進行數據處理,舉個簡單的例子:


清單 37. ExtJS 模板類使用實例
var tpl = new Ext.XTemplate( 
   "<table>", 
   "<tpl for="controls">", 
   "<tr>", 
   // 這裏的 {[this.formatValue(values.num)]} 會調用模板類中定義的同名方法
   // 其返回值會替換爲 input 節點的 value 屬性
   "<td><span>{values.label}</span> 
   <input value= '{[this.formatValue(values.num)]}' /></td>", 
   "</tr>", 
   </tpl> 
   "</table>", 
   { 
      formatValue: function(num){ 
         var num = num || 0; 
         return new String(num) + "%"; 
      } 
   } 
 ); 
 // 這裏的將一個 JSON 數據對象填充到 tpl 模板類中
 tpl.update({ 
   controls: [{ 
      label: 'A Percent', 
      num: 10 
   },{ 
      label:'B Percent',
      num: 15 
   }] 
 }); 
 // 生成的 HTML 
 <table> 
   <tr> 
      <td><span>A Percent</span><input value= ’ 10% ’ /></td> 
      <td><span>B Percent</span><input value= ’ 15% ’ /></td> 
   </tr> 
 </table>

最後,來了解下 ExtJS 控件渲染的核心思想。與 Dijit 不一樣的是,ExtJS 控件渲染不是經過直接替換 DOM 節點完成的(經過重寫 render 方法能夠改變這一思想,但不推薦,會破壞 ExtJS 頁面元素架構), 大多數狀況下都是向控件提供的父節點中添加一個新的 DOM 節點,若是沒有指定父節點,或者 ExtJS 找不到控件的父節點,控件就沒法被渲染到頁面上。正是基於這樣一種處理方式,在 ExtJS 控件的 render 過程當中,每每會經過尋找並判斷控件對象的父對象類型(前提是父對象是一個 ExtJS 控件對象而不是普通的頁面標籤元素), 來選擇不一樣的模板類進行渲染,舉個簡單的實例:


清單 37. ExtJS 中不一樣的父對象對子控件的影響示例
var mainPanel = new Ext.Panel({ 
   layout: 'auto', 
   items: [{ 
      xtype: 'textfield', 
      fieldLabel: 'Field Name'
   }] 
 }); 

 var formPanel = new Ext.Panel({ 
   layout: 'form', 
   items: [{ 
      xtype: 'textfield', 
      fieldLabel: 'Field Name'
   }] 
 })

上述 2 個 panel 對象看似基本相同,但因爲其 panel 的 layout 參數不一樣,其子對象渲染在 DOM 樹中的元素也不盡相同。textfield 對象在渲染時,會查詢其父對象的類型,當父對象的 layout 屬性爲 form 時,其 render 方法會額外添加一套 fieldLabel 模板,放在控件 DOM 節點以前,做爲控件的字段標籤,而父對象的 layout 屬性爲 auto 時,該字段標籤模板類則不會被加載。

jQuery UI 篇: jQuery UI 沒有爲控件類提供底層的模板功能支持。一個控件須要在覆寫 _create 方法時自行編寫構建控件 DOM 樹的代碼,固然您也能夠在該方法中本身實現加載模板文件的功能。其實 jQuery Core 有一個提供相似 Dijit 模板機制的插件 Templates, 然而該插件現已處於 deprecated 狀態,且 jQuery UI 並不依賴於該插件,有興趣的讀者能夠參閱 http://api.jquery.com/category/plugins/templates/

屬性獲取/配置方法

最後讓咱們來看一下各個控件庫中控件屬性獲取/配置方法的實現。

Dijit 篇:在「控件操做」這一節中,能夠看到,一個 Dijit 控件可使用 get/set 方法來獲取/配置控件的屬性。


清單 38. 獲取設置 Dijit 控件屬性
// 使用 set 設置控件屬性
 widget.set(attributeName, value); 
 // 使用 get 獲取控件屬性
 var value = widget.get(attributeName);

這是由於 dijit._WidgetBase 這個基類爲控件實現了 get/set 方法。在調用這兩個方法時,若是控件是實現過對應的 _getAttributenameAttr/_setAttributenameAttr 方法 (Attributename 爲首字母大寫後的屬性名 ), 則會調用這兩個方法,不然便直接使用默認方法獲取/設置控件屬性。藉助這一特性,能夠爲某個屬性定製其獲取/配置方法,以下面的代碼爲控件定製了 label 屬性的配置方法。


清單 39. 定製 Dijit 控件的 label 屬性設置方法
_setLabelAttr(/*String*/ label){ 
   // 僅爲控件設置 label 屬性,這一行不是必須的
   this._set("label", label); 
   // 這裏添加須要額外執行的代碼
   ... 
 }

ExtJS 篇: ExtJS 並無爲控件的每一個屬性提供對應的 get/set 方法,僅對部分改變後會引發控件邏輯或頁面元素變化的屬性添加了其對應的 get/set 方法,例如 getHeight,setHeight,getWidth,setWidth 等。大多數狀況下,ExtJS 提供的 get/set 方法已知足對控件對象的操做。對於未提供 get/set 方法的屬性,可由用戶重寫控件類來添加對應的公共方法。


清單 40. 爲 ExtJS 控件添加 setLabel 方法示例
// 添加對 label 屬性的 set 方法
 setLabel: function(label){ 
   var label = label || ''; 
   this.label = label; 
   // 添加須要對 DOM 節點改變的額外代碼
 }

每一個控件的具體屬性的獲取及配置,能夠經過查詢 ExtJS API 文檔來深刻了解。

jQuery UI 篇: jQuery UI 也沒有爲控件屬性提供傳統的 get/set 方法,而是經過 option 方法來完成對控件屬性的獲取/設置功能。


清單 41. 獲取 jQuery UI 控件屬性
// 建立控件
 $("#buttonNode").button({label: "button"}); 
 // 獲取 Button 控件屬性
 $("#buttonNode").button("option");

儘管在設置控件屬性時咱們並無顯式地調用 option 方法而是使用了 button 方法,但因爲以前已經使用 button 方法在對應節點上建立過一個 $.ui.button 控件實例,所以,第二次調用 button 方法時等價於取出以前建立的 $.ui.button 控件實例並調用其 option 方法。


清單 42. 設置 jQuery UI 控件屬性
// 設置 Button 控件屬性,等價於 buttonInstance.option({attributeName: value}); 
 $("#buttonNode").button({attributeName: value});

option 方法的具體實現是由 $.Widget 這個基類提供的,若是調用時不傳入參數,則該方法直接返回控件實例的全部屬性。若以一個屬性列表爲參數調用該方法,則會繼續執行 $.Widget 提供的另外一個方法 _setOption 來爲控件配置對應的屬性。

一樣的,用戶也能夠根據須要對屬性的配置方法進行定製。如經過如下方式定製控件 disabled 屬性的配置方法。


清單 43. 定製 jQuery UI 控件屬性設置方法
_setOption: function( key, value ) { 
   $.Widget.prototype._setOption.apply(this, arguments ); 
   if ( key === "disabled" ) { 
      // 這裏添加設置 disabled 屬性時須要額外執行的代碼
      ... 
   } 
 }

回頁首

總結

經過本文,相信您已對 Dijit,ExtJS,jQuery UI 有了必定的瞭解。

與 jQuery UI 相比,Dijit 和 ExtJS 同做爲面向對象的 JavaScript UI 庫,不少方面的處理方式仍是有些相似的,不管是控件的聲明,使用方式仍是繼承體系架構,均能找出二者類似的地方,而 jQuery UI 秉承 jQuery 一向的傳統,不管是使用方式仍是代碼風格上與前二者都有着較大的差異。深刻到實例化流程以及 DOM 樹繪製時,咱們能夠發現,三者均有各自不一樣的設計思路,從而產生的底層架構上的區別。尤爲在 DOM 樹繪製部分 ,Dijit 提供了統一的方法爲每個控件類加載其模板,用戶僅需在繪製模板時追加部分代碼來完成額外的需求,但其樣式僅能主題進行統一的控制。 而 ExtJS 中,不一樣控件的模板類須要用戶分別繪製,但其對控件模塊的清晰劃分,使用戶可對控件 CSS 樣式進行靈活配置,並有功能強大的自定義模板類支持。相比之下,在 jQuery UI 中,其簡便的架構,使用戶僅須要考慮 _create 方法的覆寫便可,但須要用戶經過大量的代碼來完成控件的繪製。

雖然因爲側重點的不一樣使得三者在功能的實現上有所區別,但假若只是完成用戶的某個需求,在 3 個框架中均能找到對應的方法。

筆者但願經過本文,給 3 個框架提供一個橫向比較的平臺,從各個角度去觀察他們在不一樣方面的表現,也給讀者在之後選擇 Web UI 框架時,提供一些參考意見。

相關文章
相關標籤/搜索