本文緊接第二部分:《BackboneJS架構的技巧及模式(2)》
http://blog.csdn.net/chszs
四、頁面部分重新整理
當第一次使用Backbone.js開發應用時,典型的視圖結構是像這樣的:
var View = Backbone.View.extend({ initialize: function(options) { this.model.on('change', this.render, this); }, template: _.template($(‘#template’).html()), render: function() { this.$el.html(template(this.model.toJSON()); $(‘#a’, this.$el).html(this.model.get(‘a’)); $(‘#b’, this.$el).html(this.model.get(‘b’)); }});
在這裡,任何對模型的改變都會觸發整個視圖的完全重新整理。我第一次用Backbone.js開發時,我是此模式的實踐者。但隨著視圖代碼的增長,我很快意識到,這種方式不利於維護或最佳化,因為模型的任一屬性發生改變時,都會觸發視圖的完全重新整理。
當我遇到這個問題,我快速用Google搜尋了一下,看別人是怎麼做的。結果找到了Ian Storm Taylor的部落格,“分解Backbone.js渲染方法”,他在其中描述了在模型中監聽單個的屬性變化,然後僅僅重新渲染相對於視圖中屬性變化的那部分。Taylor也描述了返回對象的引用,以便單個渲染函數可以很容易的連結在一起。上面的例子現在就可以修改的易於維護、效能也更優。因為我們只需做視圖的部分重新整理。
var View = Backbone.View.extend({ initialize: function(options) { this.model.on('change:a', this.renderA, this); this.model.on('change:b', this.renderB, this); }, renderA: function() { $(‘#a’, this.$el).html(this.model.get(‘a’)); return this; }, renderB: function() { $(‘#b’, this.$el).html(this.model.get(‘b’)); return this; }, render: function() { this .renderA() .renderB(); }});
我要提醒一下:有不少外掛程式,比如Backbone.StickIt和Backbone.ModelBinder,提供了模型屬性與視圖元素的索引值綁定,這會讓你省去編寫很多樣板代碼的時間,所以,如果你需要實現比較複雜的表單欄位,那麼可以看看這些外掛程式。
五、保持模型與視圖無關
正如 Jeremy Ashkenas 在 Backbone.js的GitHub問題中所指出的,Backbone.js並沒有實施資料模型與視圖層之間的真正分隔,除非模型不是引用視圖建立的。因為Backbone.js並沒有實施任何關注點分隔,所以你應該將其分離嗎?我和許多Backbone.js開發人員,如Oz Katz和Dayal,都相信答案毫無疑問是yes:模型與集合,也就是資料層,應該徹底的與綁定它們的視圖無關,保持一個清晰的關注點分離。如果你沒有遵循關注點分離,你的程式碼程式庫會很快變成意大利麵條式的代碼,而實際上是沒有人喜歡意大利麵條式的代碼。
保持模型與視圖無關將會協助你預防意大利麵條式的代碼,而沒有人喜歡意大利麵條式的代碼!
保持資料層完全與視圖層無關,這會使你建立出更具模組化、可重用和可維護的程式碼程式庫。你可以很容易地在應用程式各個地方重用和擴充模型與集合,而無需考慮它們所綁定的視圖。遵循此模式使對項目不熟悉的新手也能迅速深入到程式碼程式庫,因為他們會確切地知道哪裡發生了渲染,商務邏輯存在於哪裡。
這個模式也實現了單一職責原則——它規定了每個類應該具有單個職責,而且它的職責應該封裝在類中,因為模型與集合負責處理資料,而視圖則負責處理渲染。
六、路由的參數映射
此模式的最佳示範是理解整個例子。比如說對搜尋網頁的結果進行排序,搜尋網頁允許使用者添加兩個不同的過濾類型,foo和bar,每個類型代表不同的過濾規則。那麼,你的URL結構應該是這樣:
'search/:foo''search/:bar''search/:foo/:bar'
現在,所有路由都使用相同的視圖和模型,這樣大多數人都喜歡用同一個函數search()來實現。然而,你要是檢查過Backbone.js代碼的話,你會發現它裡面沒有任何參數映射的排序;這些參數只是從左至右依次傳入函數。這樣,為了能統一使用同一函數,你要停止建立不同的函數並正確地把參數映射到search()。
routes: { 'search/:foo': 'searchFoo', 'search/:bar': 'searchBar', 'search/:foo/:bar': 'search'},search: function(foo, bar) { },// I know this function will actually still map correctly, but for explanatory purposes, it's left in.searchFoo: function(foo) { this.search(foo, undefined);},searchBar: function(bar) { this.search(undefined, bar);},
如你所想,此模式可以使路由功能快速膨脹。當我第一次遇到這個問題時,我試圖建立一些Regex解析實際的函數定義來實現參數的映射,當然這是可以工作的——但也是受約束的。因此,我放棄了我的這個想法(或許我仍然可以通過BackboneJS的外掛程式來解決)。我進入GitHub的Issues頁面,其中Ashkenas建議應該讓所有的參數都映射到search函數。
上面的代碼現在可修改為如下的維護性更強的代碼:
routes: { 'base/:foo': 'search', 'base/:bar': 'search', 'base/:foo/:bar': 'search'},search: function() { var foo, bar, i; for(i = arguments.length - 1; i >= 0; i--) { if(arguments[i] === 'something to determine foo') { foo = arguments[i]; continue; } else if(arguments[i] === 'something to determine bar') { bar = arguments[i]; continue; } }},
此模式可以顯著減少路由的過度膨脹。但是,需注意到如果它不能識別參數,則它不會工作。比如,如果你有兩個帶ID的參數,如模式XXXX-XXXX,那麼你不能區分哪個ID是對哪個參數的響應。
七、model.fetch() 不會清除你的模型
通常它會對那些Backbone.js新手造成困擾:model.fetch()不會清除你的模型,而是繼承模型的屬性。因此,假如模型具有屬性x、y和z,你取回y和z,那麼屬性x仍然是模型中的那個x,而屬性y和z會被更新。下面的例子說明了這一點:
var Model = Backbone.Model.extend({ defaults: { x: 1, y: 1, z: 1 }});var model = new Model();/* model.attributes yields{ x: 1, y: 1, z: 1} */model.fetch();/* let’s assume that the endpoint returns this{ y: 2, z: 2,} *//* model.attributes now yields{ x: 1, y: 2, z: 2} */