建立並增強Dojo類
——譯自http://www.sitepen.com/blog/2010/07/01/creating-and-enhancing-dojo-classes/
原作者David Walsh
像所有一流的JavaScript工具包一樣,Dojo致力於將類做得儘可能靈活,因為它知道不同的使用者可能對一個類及其方法是如何工作的抱有完全不同的看法。幸運的是,Dojo提供了大量的方法使您能夠繼承或修改類。現在我們就來研究其中的一些方法,它們可以讓你隨心所欲地擺弄Dojo類。
建立Dojo子類
建立Dojo類或子類的一個典型方法是用dojo.declare。dojo.declare把類註冊到你所指定的名字空間中,並且能夠繼承任意數量的類(由第二個參數傳入)。
以下代碼展示了建立子類的基本寫法:
//dojo.declare(’your.name.space’,superClass,{ customProperties });<br />//或者:<br />//dojo.declare(’your.name.space’,[superClass1,superClass2,superClass3],{ customProperties });<br />dojo.provide(‘davidwalsh.Menu’);<br />dojo.declare(‘davidwalsh.Menu’,dijit.Menu,{<br /> /* 添加你自訂的屬性和方法 */<br /> myCustomProperty: true,<br /> myCustomMethod: function() {<br /> /* 做一些有趣的事…… */<br /> }<br /> //dijit.Menu的所有屬性和方法都被自動繼承<br />});
以上代碼建立的新類davidwalsh.Menu就是一個新的自訂Dojo類,它繼承了dijit.Menu類的所有方法和屬性,而且還添加了一個新的自訂屬性以及一個自訂方法,可以用來做任何事情。既然我們已經知道如何建立子類了,那就讓我們來寫一個實用一點的davidwalsh.Menu吧:
dojo.provide(‘davidwalsh.Menu’);<br />dojo.declare(‘davidwalsh.Menu’,dijit.Menu,{<br /> //一個新的選項<br /> allowSubmenuHover: true,<br /> //另一個新的選項<br /> popupDelay: 500,<br /> //重載dijit.Menu的方法<br /> onItemHover: function(item) {<br /> if(this.isActive || this.allowSubmenuHover) {<br /> this.focusChild(item);<br /> //使用新的設定來觸發快顯功能表<br /> if(this.focusedChild.popup &&<br /> !this.focusedChild.disabled &&<br /> !this.hover_timer){<br /> this.hover_timer = setTimeout(<br /> dojo.hitch(this, ‘_openPopup’),<br /> this.popupDelay);<br /> }<br /> }<br /> if(this.focusedChild){<br /> this.focusChild(item);<br /> }<br /> this._hoveredChild = item;<br /> }<br />});
davidwalsh.Mene是dijit.Menu類的增強版。它具有兩個新的選項,並重載了一個dijit.Menu的方法。其目的在於提供一種在滑鼠移至上方時而非單擊時開啟快顯功能表項的菜單。
你可能在想,怎樣在子類的方法裡調用父類的方法呢?這也很簡單:
//……一些其他方法<br />someMethod: function() {<br /> /* 在這裡做一些你想做的事…… */<br /> // 調用父類的someMethod方法,從而重用原有的功能。<br /> var result = this.inherited(arguments);<br /> /* 在這裡做另一些你想做的事…… */<br />}<br />//……另一些其他方法
可見,建立子類易如反掌。但如果你只需要修改一個現存的Dojo類,又該怎麼做呢?答案是打補丁!
原型修改(Monkey Patching)
有時,繼承一個現有的Dojo類並不是最好的選擇(甚至有可能根本無法這樣做)。你很可能處於這樣一個境地,只能給現有的Dojo類打補丁,這時Monkey Patching就是最理想的選擇。所謂Monkey Patching就是一個修改現有對象(在這裡指Dojo類)的原型的過程。這一做法的優點有:
- 這個類型的所有現存對象都被同時修改了。
- 不需要訪問Dojo核心檔案.
- 由於你並沒有修改Dojo的核心檔案,升級Dojo版本就變得相對容易,因為你不用跟蹤你以前的改動。
- 你的補丁也將具有更好的可移植性,因為它們不是直接放在Dojo核心檔案裡。
以下代碼展示了Monkey Patching的模式:
(function(){<br /> //儲存原有的原型方法<br /> var oldPrototypeSomeMethod =<br /> dijit.someDijit.prototype.someMethod;<br /> //修改原型<br /> dijit.someDijit.prototype.someMethod = function(){<br /> /* ……這裡是一些新代碼…… */<br /> // 僅當你希望保留原有功能時,調用剛才你儲存的原有原型方法<br /> oldPrototypeSomeMethod.call(this, arguments);<br /> };<br />})();
現在我們來看一個實際例子。我最近正在使用FilteringSelect控制項,我發現如果srcNode(也就是select元素)裡的第一個option元素沒有value屬性(或者value為空白字串),那麼這個元素的label就不會顯示。這是一個非常奇怪的bug,並且肯定不是一個符合使用者預期的行為。我所能做的就是給這個控制項類的postMixInProperties方法打上補丁來修複這個問題:
(function(){<br /> var dffsp = dijit.form.FilteringSelect.prototype;<br /> //儲存原有的原型方法<br /> var oldPMIP = dffsp.postMixInProperties;<br /> //修改控制項原型<br /> dffsp.postMixInProperties = function(){<br /> //如果select元素當前沒有值,並且其首個選項的value屬性是Null 字元串,就把控制項的displayedValue設為初始標籤。<br /> if(!this.store && this.srcNodeRef.value == ”){<br /> var srcNodeRef = this.srcNodeRef,<br /> nodes = dojo.query("> option[value='']", srcNodeRef);<br /> if(nodes.length){<br /> this.displayedValue =<br /> dojo.trim(nodes[0].innerHTML);<br /> }<br /> }<br /> // 調用原有的方法。我們仍然需要這些原有功能。<br /> oldPMIP.call(this, arguments);<br /> };<br />})();
這隻是使用Monkey Patching的好例子之一。雖然打補丁可能看起來屬於不太優雅的技術,但它的確是定製你手裡Dojo包的一個必要方法。
擴充Dojo類
Dojo.extend方法允許我們為類的原型添加新方法,從而為該類的所有執行個體提供這些方法。如果傳給dojo.extend的某個方法在類中已經存在同名方法,那它將覆蓋這個原有方法。
下面的例子展示了如果和擴充dijit.Menu類,從而使快顯功能表在滑鼠懸浮在標籤上時顯示,而不是單擊時。
dojo.extend(dijit.Menu,{<br /> allowSubmenuHover: true, //一個新的設定選項<br /> popupDelay: 500, //一個新的設定選項<br /> onItemHover: function() { //重載父類方法<br /> if(this.isActive || this.allowSubmenuHover) {<br /> this.focusChild(item);<br /> //使用新選項觸發快顯功能表<br /> if(this.focusedChild.popup &&<br /> !this.focusedChild.disabled &&<br /> !this.hover_timer){<br /> this.hover_timer = setTimeout(dojo.hitch(<br /> this, ‘_openPopup’), this.popupDelay);<br /> }<br /> }<br /> if(this.focusedChild){<br /> this.focusChild(item);<br /> }<br /> this._hoveredChild = item;<br /> }<br />});
注意到原有的onItemHover方法並沒有被儲存下來並在以後使用,而是整個原型都被重寫了。因為我們正是要拋棄這個方法原來的功能。現在我們擁有了一個滿足我們需求的dijit.Menu類,並且我們的Dojo包中的檔案並沒有受到幹擾。
繼承,擴充,還是打補丁?
對於一個類,何時應該擴充何時應該補丁並沒有一個硬性的規定。但我的確有幾點建議:
- 不要修改任何Dojo核心檔案,必要時打補丁或做擴充。
- 如果你需要訪問原有的的原型對象,使用打補丁的方法。
- 當希望在多重專案之間重用代碼時,使用你自訂的名字空間建立子類。
- 如果可移植性是一個很重要的因素,使用擴充類的方法。
盡量擴充!
擴充Dojo類是修複bug、增強Dojo內建類、以及避免重複代碼的最佳途徑。Dojo裡沒有任何限制,除了那些你強加給它的!