Backbone.js的一些使用技巧
自從3年前Backbone.js發(fā)布第一版以來,Backbone.js就成為一個(gè)流行的開源JavaScript “MV*”框架,并獲得人們的青睞。盡管Backbone.js給JavaScript應(yīng)用提供了框架,但是它仍然給開發(fā)者留有很多設(shè)計(jì)模式供選擇,不管怎樣,當(dāng)開發(fā)者第一次使用Backbone.js時(shí)還會產(chǎn)生很多普遍的問題的。
因此,在這篇文章中,我們將介紹很多不同的設(shè)計(jì)模式供你在Backbone.js應(yīng)用中使用,而且我們也會一同來看看對于開發(fā)者來說會產(chǎn)生很多普遍的有關(guān)性能伸縮的問題。
對象深度拷貝
JavaScript對待所有原生類型變量是傳值。所以,當(dāng)變量被引用時(shí)就傳遞了變量的值。
var helloWorld = “Hello World”; var helloWorldCopy = helloWorld;
舉個(gè)例子,上面的代碼將變量helloWorldCopy的值設(shè)置為變量helloWorld的值。這樣, 自從它的值被復(fù)制之后,所有修改helloWorldCopy的值不會修改helloWorld的值。JavaScript對待所有非原始類型的變量時(shí)傳引用,這就意味著當(dāng)變量傳遞的時(shí)候?qū)鬟f內(nèi)存地址引用。
var helloWorld = { ‘hello': ‘world' } var helloWorldCopy = helloWorld;
舉個(gè)例子,上面的代碼將設(shè)置helloWorldCopy為helloWorld的引用,而且,也許你會猜到任何修改helooWorldCopy的值都會直接導(dǎo)致helloWorld值的變化。如果你想要helloWorld的拷貝,你可以創(chuàng)建一個(gè)拷貝對象即可。
也許你會想到“為什么Backbone.js可以解釋為所有的工作都是通過傳遞引用?”事實(shí)上,Backbone.js不會拷貝對象,這將意味著如果你從模型里調(diào)用.get()方法獲得一個(gè)對象,任何給這個(gè)對象的修改都會直接修改原來的對象。讓我們一起來看一個(gè)例子來闡明哪里會發(fā)生這樣的情況。如果你有個(gè)如下的Person模型:
var Person = Backbone.Model.extend({ defaults: { 'name': 'John Doe', 'address': { 'street': '1st Street' 'city': 'Austin', 'state': 'TX' 'zipCode': 78701 } } });
這樣你就創(chuàng)建了一個(gè)新的person對象:
var person = new Person({ 'name': 'Phillip W' });
現(xiàn)在我們來對新對象的一些屬性進(jìn)行操作操作:
person.set('name', 'Phillip W.', { validate: true });
上面的代碼成功的給person對象的name屬性賦了值?,F(xiàn)在我們在來操作person對象的地址屬性。當(dāng)然,在我們這樣做之前先驗(yàn)證一下地址屬性。
var Person = Backbone.Model.extend({ validate: function(attributes) { if(isNaN(attributes.address.zipCode)) return "Address ZIP code must be a number!"; }, defaults: { 'name': 'John Doe', 'address': { 'street': '1st Street' 'city': 'Austin', 'state': 'TX' 'zipCode': 78701 } } });
現(xiàn)在,讓我們試圖給地址屬性設(shè)置一個(gè)不正確的ZIP代碼。
var address = person.get('address'); address.zipCode = 'Hello World'; // Raises an error since the ZIP code is invalid person.set('address', address, { validate: true }); console.log(person.get('address')); /* Prints an object with these properties. { 'street': '1st Street' 'city': 'Austin', 'state': 'TX' 'zipCode': 'Hello World' } */
這將會怎樣呢?我們的驗(yàn)證出現(xiàn)了錯誤!為什么屬性依舊被改變了?前邊我們說過,Backbone.js不會拷貝模型屬性;它會返回你所請求的一切。這樣,你也許會猜到,如果你需要一個(gè)對象,你將得到這個(gè)對象的引用,對這個(gè)對象的任何操作都會直接改變模型里的對象。如果你要debug,這可能將把你帶入到無底的兔子黑洞。
這個(gè)問題對于新的Backbone.js使用要引起注意,甚至對于老練的JavaScript程序員有時(shí)也會沒有提防。這個(gè)問題在GitHub的Backbone.js討論組中有很激烈的討論。正如Jeremy Ashkenas指出,執(zhí)行一個(gè)深的對象引用是個(gè)很難解決的難題,一個(gè)很深的對象引用是要花費(fèi)很大代價(jià)的。
幸運(yùn)的,jQuery 提供了一個(gè)深度拷貝功能來實(shí)現(xiàn),$.extend. 如同, Underscore.js ,一個(gè)Backbone.js的依靠,提供_.extend 方法,但是我必須避免使用它,因?yàn)樗鼪]有執(zhí)行一份是個(gè)深度的復(fù)制,Lo-Dash, Underscore.js的一個(gè)分叉版本,提供了對象一個(gè)深度克隆的_.clone 方法的選項(xiàng)。然而,我使用 $.extend 方法的模型使用的語法規(guī)則去執(zhí)行一個(gè)任意對象的深度克隆。記得通過后,結(jié)果它執(zhí)行的是一個(gè)深度的克隆方法
var address = $.extend(true, {}, person.address);
我們現(xiàn)在快速準(zhǔn)確的復(fù)制一個(gè)theaddressobject?,并且我們能夠更改它對于我們要點(diǎn)沒有包括在內(nèi)的我們不用擔(dān)心會更改它原有的模型。你必須要意思到這個(gè)父工廠對于上面所有的事例因?yàn)樗械牡刂穼ο蟪蓡T都是不可變的(numbers, strings, etc.),與此同時(shí)這上面所有的事例工廠當(dāng)你要深度復(fù)制對象里面包含的對象時(shí)你都必須小心的使用。你必須也要知道一個(gè)小小的性能影響都來自于執(zhí)行一個(gè)深度的克隆,但是我從來沒有看到過很明顯的問題。然而,如果你要深度的克隆一個(gè)大對象或者成千上萬的對象所有的立即復(fù)制,你將有可能做大量的性能分析。這將領(lǐng)導(dǎo)我們直接到下一個(gè)模式。
為對象創(chuàng)建外觀
在現(xiàn)實(shí)世界中,需求經(jīng)常變化,JavaScript對象符號(或者說JSON)也是一樣,這些對象是由模型和集合所在的端點(diǎn)返回的。這或許會成為你的基礎(chǔ)代碼中的一個(gè)真正的大麻煩,如果你的視圖與底層的數(shù)據(jù)模型是緊耦合的話。因此,我為所有對象創(chuàng)建了getters和setters。
支持這個(gè)模式的人非常多。如果任何底層的數(shù)據(jù)結(jié)構(gòu)改變了,那么視圖層并不需要更新許多;你將有一個(gè)數(shù)據(jù)的訪問點(diǎn),所以你不太可能忘記做一個(gè)深度拷貝,你的代碼將會更易于維護(hù)更易于調(diào)試。負(fù)面因素在于這個(gè)模式可能導(dǎo)致模型或集合的一點(diǎn)點(diǎn)膨脹。
我們看一個(gè)例子來闡明這個(gè)模式。想像我們有一個(gè)Hotel模型,包含有rooms和目前可獲得的rooms,而且我們希望可以通過床位大小來獲得rooms。
var Hotel = Backbone.Model.extend({ defaults: { "availableRooms": ["a"], "rooms": { "a": { "size": 1200, "bed": "queen" }, "b": { "size": 900, "bed": "twin" }, "c": { "size": 1100, "bed": "twin" } }, getRooms: function() { $.extend(true, {}, this.get("rooms")); }, getRoomsByBed: function(bed) { return _.where(this.getRooms(), { "bed": bed }); } } });
現(xiàn)在我們假設(shè)明天你就要發(fā)布你的代碼,而你又發(fā)現(xiàn)端點(diǎn)開發(fā)者忘記告訴你rooms的數(shù)據(jù)結(jié)構(gòu)改變了,由一個(gè)對象變?yōu)橐粋€(gè)數(shù)組。你的代碼現(xiàn)在看起來會像下面這樣。
var Hotel = Backbone.Model.extend({ defaults: { "availableRooms": ["a"], "rooms": [ { "name": "a", "size": 1200, "bed": "queen" }, { "name": "b", "size": 900, "bed": "twin" }, { "name": "c", "size": 1100, "bed": "twin" } ], getRooms: function() { var rooms = $.extend(true, {}, this.get("rooms")), newRooms = {}; // transform rooms from an array back into an object _.each(rooms, function(room) { newRooms[room.name] = { "size": room.size, "bed": room.bed } }); }, getRoomsByBed: function(bed) { return _.where(this.getRooms(), { "bed": bed }); } } });
我們僅僅更新了一個(gè)函數(shù),以便將Hotel的結(jié)構(gòu)轉(zhuǎn)變?yōu)檫@個(gè)應(yīng)用的其余部分所期望的結(jié)構(gòu),同時(shí)整個(gè)應(yīng)用仍然像我們所期待的一樣運(yùn)作。如果這里沒有一個(gè)getter,我們很可能不得不為rooms更新每個(gè)訪問點(diǎn)。理想情況下,你會希望更新所有的函數(shù),以適應(yīng)新的數(shù)據(jù)結(jié)構(gòu),但如果你在時(shí)間方面有壓力急于發(fā)布的話,這個(gè)模式將可以拯救你。
離題說一句,這個(gè)模式既可以被認(rèn)為是裝飾模式,因?yàn)樗[藏了創(chuàng)建對象拷貝的復(fù)雜性,也可以認(rèn)為是橋接模式,因?yàn)樗梢杂脕韺?shù)據(jù)轉(zhuǎn)換為所期望的形式。一個(gè)好的經(jīng)驗(yàn)是對任何對象元素使用getters 和setters 。
存儲數(shù)據(jù)不是通過服務(wù)器保存
盡管Backbone.js有模型和集合映射的規(guī)定去具象狀態(tài)的傳輸(or REST-ful)的端點(diǎn),你將花大量的時(shí)間去找你想要的存儲數(shù)據(jù)在你的模型或者不是在服務(wù)器上的連接。另外一些關(guān)于Backbone.js的文章,例如“Backbone.js Tips: Lessons From the Trenches” 是通過SupportBee的Prateek Dayal ,這個(gè)模式還有其他的描述。讓我們一起來快速的看一個(gè)小例子來幫助我們說明它可能會派上用場。假設(shè)你有一個(gè)集合。
<ul> <li><a href="#" data-id="1">One</a></li> <li><a href="#" data-id="2">Two</a></li> . . . <li><a href="#" data-id="n">n</a></li> </ul>
當(dāng)使用者點(diǎn)擊其中一個(gè)項(xiàng)目時(shí),這個(gè)項(xiàng)目成為了被選中狀態(tài)并且對于使用者作為選中項(xiàng)目是通過 aselectedclass 添加的是可視化的。以下這是一種方式:
var Model = Backbone.Model.extend({ defaults: { items: [ { "name": "One", "id": 1 }, { "name": "Two", "id": 2 }, { "name": "Three", "id": 3 } ] } }); var View = Backbone.View.extend({ template: _.template($('#list-template').html()), events: { "#items li a": "setSelectedItem" }, render: function() { $(this.el).html(this.template(this.model.toJSON())); }, setSelectedItem: function(event) { var selectedItem = $(event.currentTarget); // Set all of the items to not have the selected class $('#items li a').removeClass('selected'); selectedItem.addClass('selected'); return false; } }); <script id="list-template" type="template"> <ul id="items"> <% for(i = items.length - 1; i >= 0; i--) { %> <li> <a href="#" data-id="<%= item[i].id %>"><%= item[i].name %></a></li> <% } %></ul> </script>
現(xiàn)在我們能夠很容易的判斷被選中的項(xiàng)目,并且我們沒有必要通過對象模型去判斷。這種模式對于存儲無用的數(shù)據(jù)是非常有用的以至于 你可能非常想要去跟蹤;請記住你能夠創(chuàng)建一個(gè)模型并且沒有必要去關(guān)聯(lián)于他們存儲的一些無用的圖像數(shù)據(jù)。
var View = Backbone.View.extend({ initialize: function(options) { // Re-render when the model changes this.model.on('change:items', this.render, this); }, template: _.template($('#list-template').html()), events: { "#items li a": "setSelectedItem" }, render: function() { $(this.el).html(this.template(this.model.toJSON())); }, setSelectedItem: function(event) { var selectedItem = $(event.currentTarget); // Set all of the items to not have the selected class $('#items li a').removeClass('selected'); selectedItem.addClass('selected'); // Store a reference to what item was selected this.selectedItemId = selectedItem.data('id')); return false; } });
現(xiàn)在我們可以很容易的確定哪些項(xiàng)已經(jīng)被選中,并且我們沒有必要通過這些對象模型來了解。這個(gè)模式對于存儲無用的數(shù)據(jù)是非常有用的,請記住,您可以創(chuàng)建不一定有端點(diǎn)相關(guān)聯(lián)的存儲無關(guān)的視圖數(shù)據(jù)的模型和集合。
這種模式的缺點(diǎn)是你存儲了無用的數(shù)據(jù)在你的模型或者集合中,它們不能真正意義上的追隨一個(gè)平靜的架構(gòu)是因?yàn)樗鼈儾粫昝赖娜ビ成湓趙eb資源上;另外,這個(gè)模式會引起一些很膨脹的在你的模型中;;并且當(dāng)你保存你的模型的時(shí)候如果你的端點(diǎn)嚴(yán)格的只接受JSON數(shù)據(jù)它會引起一個(gè)很大的煩惱。
你可能會問你自己,“我如何確定我是否應(yīng)該講把額外的數(shù)據(jù)放進(jìn)視圖或者是模型中?”。如果額外的屬性你將要增加的是圍繞性的呈現(xiàn),例如一個(gè)容器的高度,我們應(yīng)該要添加它的圖形。如果這個(gè)屬性跟底層的數(shù)據(jù)模型有一些關(guān)系,然后你想要將它放進(jìn)這個(gè)模型中。例如,如果上面的例子更多的顯露出,因?yàn)槟承┰蛭覂H僅只希望用戶通過從模型返回的項(xiàng)目列表中選擇一個(gè)特殊的項(xiàng),我可能會增加這種邏輯模型。總而言之,大多數(shù)的事情,它實(shí)際上取決于這種依賴。你能夠?yàn)楸3帜愕哪P投q論并且你可以認(rèn)為保持你的觀點(diǎn)是可能的并且把盡可能多的邏輯放進(jìn)你的模型中。
渲染部分視圖,而不是整個(gè)視圖
當(dāng)你第一次開始開發(fā)Backbone.js應(yīng)用時(shí),典型的視圖結(jié)構(gòu)是像這樣的:
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(chǎn)')); $(‘#b', this.$el).html(this.model.get(‘b')); } });
在這里,任何對模型的改變都會觸發(fā)對視圖的一個(gè)全面的重新渲染。我第一次用Backbone.js開發(fā)時(shí),我是這個(gè)模式的實(shí)踐者。但隨著視圖代碼的增長,我迅速的意識到,這種方法不利于維護(hù)或優(yōu)化,因?yàn)楫?dāng)模型的任何一個(gè)屬性發(fā)生變化時(shí),視圖將會完全的重新渲染。
當(dāng)我遇到這個(gè)問題,我迅速的用Google搜索了一下,看看別人是怎么做的,結(jié)果找到了Ian Storm Taylor的博客,“分解你的Backbone.js渲染方法”,他在其中描述了在模型中監(jiān)聽單獨(dú)的屬性變化,然后僅僅重新渲染相對于變化屬性的視圖部分。Taylor也描述了返回對象的引用,以便單獨(dú)的渲染函數(shù)可以很容易的鏈接在一起。上面的例子現(xiàn)在現(xiàn)在就變得更易于維護(hù),性能更優(yōu)。因?yàn)槲覀儍H僅更新了模型變化的屬性相對應(yīng)的視圖部分。
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(chǎn)')); return this; }, renderB: function() { $(‘#b', this.$el).html(this.model.get(‘b')); return this; }, render: function() { this .renderA() .renderB(); } });
我應(yīng)該說一下有許多插件,比如Backbone.StickIt和Backbone.ModelBinder,提供了模型屬性與視圖元素的鍵-值綁定,這會讓你省去編寫許多樣板代碼,如果你具有復(fù)雜的表單字段檢驗(yàn)一下它們。
保持模型與視圖無關(guān)
正如 Jeremy Ashkenas 在 Backbone.js的 GitHub問題 之一中所指出的,Backbone.js 并沒有實(shí)施數(shù)據(jù)與視圖層之間關(guān)注點(diǎn)的任何真正分離,除非模型未引用視圖而創(chuàng)建。因?yàn)锽ackbone.js并沒有執(zhí)行一個(gè)關(guān)注點(diǎn)分離,所以你應(yīng)該將其分離嗎?我和許多其他的Backbone.js開發(fā)人員,如Oz Katz 和 Dayal ,都相信答案毫無疑問是yes:模型與集合,也就是數(shù)據(jù)層,應(yīng)該徹底的與綁定到它們的視圖無關(guān),保持一個(gè)清晰的關(guān)注點(diǎn)分離。如果你沒有遵循關(guān)注點(diǎn)分離,你的基礎(chǔ)代碼將很快變成意大利面條式的代碼,而沒有人喜歡意大利面條式的代碼。
保持模型與視圖無關(guān)將會幫助你預(yù)防意大利面條式的代碼,而沒有人喜歡意大利面條式的代碼!
保持你的數(shù)據(jù)層徹底的與視圖層無關(guān),這將會使你創(chuàng)建出更具模塊化,可復(fù)用與可維護(hù)的基礎(chǔ)代碼。你可以非常容易的在應(yīng)用程序各個(gè)地方復(fù)用與擴(kuò)展模型和集合,而不需要考慮它們所綁定的視圖。遵循這個(gè)模式使對你項(xiàng)目不熟悉的開發(fā)者能迅速的深入到基礎(chǔ)代碼之中,因?yàn)樗麄儠_切的知道哪里發(fā)生了渲染,哪里存在有你的應(yīng)用的所有商務(wù)邏輯。
這個(gè)模式也執(zhí)行了單一職責(zé)原則,規(guī)定了每個(gè)類應(yīng)該具有一個(gè)單一的職責(zé),而且它的職責(zé)應(yīng)該封裝與這個(gè)類之中,因?yàn)槟P团c集合要處理數(shù)據(jù),而視圖要處理渲染。
路由中的參數(shù)
最好的演示這個(gè)模式工作方式是舉個(gè)例子。比如說你需要對搜索頁面進(jìn)行排序,每個(gè)搜索頁面都允許用戶添加兩個(gè)不同的過濾類型foo和bar,每個(gè)類型代表不同的觀點(diǎn)。
因此,你的URL結(jié)構(gòu)將會呈現(xiàn)如下:
'search/:foo' 'search/:bar' 'search/:foo/:bar'
現(xiàn)在,所有的路由都用的是同一個(gè)試圖和模型,這樣大多數(shù)人喜歡用同一個(gè)函數(shù)search()來實(shí)現(xiàn)。然而,你要是檢查過Backbone.js代碼的話,你會發(fā)祥它里面沒有排序的參數(shù)映射;這些參數(shù)只是從左至右依次傳入函數(shù)。這樣,為了都能統(tǒng)一使用一個(gè)函數(shù),你就要停止創(chuàng)建不同的函數(shù)正確的來為search()匹配參數(shù)。
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); },
你也許能想象的到,這個(gè)模式可以使路由功能很快膨脹。當(dāng)我第一次遇到這個(gè)問題時(shí),我試圖創(chuàng)建了一些用正則表達(dá)式定義的解析函數(shù)來“神奇”的去匹配參數(shù),當(dāng)然這個(gè)是可以工作的-但這也是有約束條件的。這樣,我廢棄了這個(gè)想法(有時(shí),我仍然可以用Backbone插件來解決)。我進(jìn)入GitHub中的一個(gè) 議題,其中Ashkenas建議應(yīng)該讓所有的參數(shù)都和search函數(shù)匹配。
上面的代碼現(xiàn)在轉(zhuǎn)變?yōu)橄旅婢S護(hù)性更強(qiáng)的樣子:
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); },
這種模式可以戲劇性的減少路由的過分膨脹。然而,需要注意到它不會服務(wù)于不能區(qū)別的參數(shù)。比如,如果你有兩個(gè)作為ID的參數(shù),如模式XXXX-XXXX,你不能區(qū)分哪個(gè)ID是對哪個(gè)參數(shù)的回應(yīng)。
model.fetch() 不會清除你的模型
這通常會將那些Backbone.js的新手給絆倒:model.fetch()并不能丟掉你的模型,而是擴(kuò)展了你的模型的屬性。因此,如果你的模型具有屬性x,y和z,你獲取到y(tǒng)和z,那么x將仍然是模型中的那個(gè)x,只有y和z會被更新。下面的例子將這個(gè)概念形象化了。
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 } */
PUTs 需要一個(gè) ID 屬性
這一條也經(jīng)常將Backbone.js的新手絆倒。要想在調(diào)用.save()的時(shí)候讓模型發(fā)送一個(gè)HTTP PUT請求,你的模型需要有一個(gè)ID屬性集。記得HTTP PUT謂詞是設(shè)計(jì)來做更新的吧,所以發(fā)送一個(gè)PUT請求,你的模型需要有一個(gè)ID,這么做是有意義的。在理想的世界里,你的所有模型都具有一個(gè)名為ID的完美的ID屬性,但是你從端點(diǎn)接收到的JSON數(shù)據(jù)可能并不總是具有完美命名的IDs。
因此,如果你需要更新一個(gè)模型,請?jiān)诒4嬷按_認(rèn)模型上有ID。Backbone.js 的0.5以及更高版本允許你用id屬性來更新模型的ID屬性名稱,如果你的端點(diǎn)返回的不是名為id的IDs的話。
如果困頓于使用的是版本低于0.5的Backbone.js,我建議你修改你的模型或集合的parse函數(shù),以便將你期望的ID屬性映射到屬性ID。這里有一個(gè)快速上手的例子,說明了你應(yīng)怎樣修改parse函數(shù)來做到這一點(diǎn)。我們假設(shè)你有一個(gè)cars的集合,它的IDs是carID。
parse: function(response) { _.each(response.cars, function(car, i) { // map the returned ID of carID to the correct attribute ID response.cars[i].id = response.cars[i].carID; }); return response; },
頁面加載時(shí)創(chuàng)建模型數(shù)據(jù)
有時(shí)你會發(fā)現(xiàn)你的模型或者集合需要在頁面加載時(shí)被初始化賦值。許多關(guān)于Backbone.js模式的文章,例如Rico Sta Cruz的 “Backbone 模式” 和 Katz的 “ 避免常見的Backbone.js陷阱” ,討論了這種模式。這種模式實(shí)現(xiàn)很容易,只需在頁面中內(nèi)聯(lián)一段腳本,通過你選擇的服務(wù)端語言,將單個(gè)模型屬性或者JSON形式的數(shù)據(jù)呈現(xiàn)出來。例如,在Rails語言中,我采用下面方法之一:
// a single attribute var model = new Model({ hello: <%= @world %> }); // or to have json var model = new Model(<%= @hello_world.to_json %>);
應(yīng)用這種模式可以通過“立即的”渲染頁面,改善你的搜索引擎排名,而且它也可以通過限制應(yīng)用初始化HTTP請求的方式,大大縮短你的應(yīng)用啟動與運(yùn)行所需要的時(shí)間。
處理失敗的模型屬性驗(yàn)證
很多時(shí)候,你會想知道是哪個(gè)模型屬性驗(yàn)證失敗了。例如,如果你有一個(gè)極其復(fù)雜的表單,你或許想知道哪個(gè)模型屬性驗(yàn)證失敗,這樣你就可以將這個(gè)屬性對應(yīng)的輸入字段高亮顯示。不幸的是,提醒視圖到底是哪個(gè)模型屬性驗(yàn)證失敗并沒有直接集成于Backbone.js,但是你可以用一些不同的模式去處理這個(gè)問題。
返回一個(gè)錯誤對象
一個(gè)給視圖提醒哪個(gè)模型屬性驗(yàn)證失敗的模式是,返回一個(gè)對象,其中包含某種標(biāo)志,它詳細(xì)的記錄了哪個(gè)屬性驗(yàn)證為失敗,就像下面這樣:
// Inside your model validate: function(attrs) { var errors = []; if(attrs.a < 0) { errors.push({ 'message': 'Form field a is messed up!', 'class': 'a' }); } if(attrs.b < 0) { errors.push({ 'message': 'Form field b is messed up!', 'class': 'b' }); } if(errors.length) { return errors; } } // Inside your view this.model.on('invalid', function(model, errors) { _.each(errors, function(error, i) { $(‘.' + error.class).addClass('error'); alert(error.message); }); });
這個(gè)模式的優(yōu)點(diǎn)在于,你是在一個(gè)地方處理所有不合法的消息。缺點(diǎn)在于,如果你以不同的方式處理不合法的屬性的話,你的invalid方法可能會成為一個(gè)很大的switch或者if語句。
廣播自定義Error事件
我的一個(gè)朋友,Derick Bailey,推薦了一個(gè)可替代模式,就是為每個(gè)模型屬性觸發(fā)自定義的errors事件。這將允許你的視圖能夠針對單獨(dú)的屬性綁定到特定的error事件:
// Inside your model validate: function(attrs) { if(attrs.a < 0) { this.trigger(‘invalid:a', 'Form field a is messed up!', this); } if(attrs.b < 0) { this.trigger(‘invalid:b', 'Form field b is messed up!', this); } } // Inside your view this.model.on('invalid:a', function(error) { $(‘a(chǎn)').addClass('error'); alert(error); }); this.model.on('invalid:b', function(error) { $(‘b').addClass('error'); alert(error); });
這個(gè)模式的優(yōu)點(diǎn)在于,你的視圖明確的綁定到它們所綁定到的error類型,而且如果你對每一種屬性error有特定的指令的話,它可以清理你的視圖部分代碼,使之更易于維護(hù)。這個(gè)模式的一個(gè)不好的地方在于,如果在你處理不同的屬性error時(shí)并沒有太多的不同的話,你的視圖可能會變得極為膨脹。
這兩種模式都有其利弊,你應(yīng)該考慮清楚哪個(gè)模式對你的應(yīng)用案例是最優(yōu)的。如果你按照同樣的方式處理所有失敗的驗(yàn)證,那么第一個(gè)方法可能是最好的;如果你對每個(gè)模型屬性有特定的UI變化,那么后一種方法更好。
HTTP狀態(tài)代碼200所觸發(fā)的錯誤
如果你的瀏覽器端模型或者集合收到了無效的JSON,盡管HTTP的狀態(tài)代碼是200,但瀏覽器端依然會觸發(fā)一個(gè)“錯誤”事件。這種事件常發(fā)生于本地模擬JSON數(shù)據(jù)造成的。那么,一個(gè)好的方法就是讀取經(jīng)過 JSON 驗(yàn)證器驗(yàn)證了的模擬JSON數(shù)據(jù)文件?;蛘邚哪愕腎DE獲得相應(yīng)的 插件來及時(shí)獲取格式錯誤的JSON信息。
創(chuàng)建一個(gè)一般性錯誤顯示模式
創(chuàng)建一個(gè)常見錯誤顯示代碼可以節(jié)省你的時(shí)間以及創(chuàng)建一個(gè)統(tǒng)一的模式來處理、可視化錯誤信息,而且它可以增加開發(fā)者的經(jīng)驗(yàn)。我之前開發(fā)的每一個(gè)Backbone.js應(yīng)用中我都會創(chuàng)建一個(gè)可以處理alert的視圖:
var AlertView = Backbone.View.extend({ set: function(typeOfError, message) { var alert = $(‘.in-page-alert').length ? $(‘.in-page-alert'): $(‘.body-alert'); alert .removeClass(‘error success warning') .addClass(typeOfError) .html(message) .fadeIn() .delay(5000) .fadeOut(); } });
上面的代碼首先會檢查是否已在視圖代碼中創(chuàng)建了指定視圖in-page-alert div。如果沒有,則接著查看一般性的在其它地方聲明的body-alert div。這樣可以讓你發(fā)送具有一致性的錯誤信息以及當(dāng)你忘記指定一個(gè)in-page-alert div時(shí)提供有用且可靠的信息。如下面的模式簡化了讓你怎樣在你的試圖中處理錯誤信息:
var alert = new AlertView(); this.model.on('error', function(model, error) { alert.set('TYPE-OF-ERROR', error); });
單頁面應(yīng)用中更新瀏覽器頁面標(biāo)題
這是一個(gè)比任何東西都重要的可用性問題。如果你正在開發(fā)一個(gè)單頁面應(yīng)用程序,謹(jǐn)記更新每個(gè)頁面的標(biāo)題。我寫過一個(gè)的插件(Backbone.js Router Title Helper)來擴(kuò)展 backbone.js router 的功能。它通過一個(gè) Map 對象來控制路由,鍵來代表路由函數(shù)的名字,值則映射到頁面的標(biāo)題。
Backbone.Router = Backbone.Router.extend({ initialize: function(options){ var that = this; this.on('route', function(router, route, params) { if(that.titles) { if(that.titles[router]) document.title = that.titles[router]; else if(that.titles.default) document.title = that.titles.default; else throw 'Backbone.js Router Title Helper: No title found for route:' + router + ' and no default route specified.'; } }); } });
單頁面應(yīng)用中的緩存對象
當(dāng)我們談?wù)搯雾撁鎽?yīng)用時(shí),另一個(gè)叫緩存對象模式你將會經(jīng)常用到!下面的例子直截了當(dāng)而且簡單:
// Inside a router initialize: function() { this.cached = { view: undefined, model: undefined } }, index: function(parameter) { this.cached.model = this.cached.model || new Model({ parameter: parameter }); this.cached.view = this.cached.view || new View({ model: this.cached.model }); }
這個(gè)模式可以加速你得應(yīng)用,因?yàn)槟悴挥弥貜?fù)初始化你得Backbone.js對象。然而,它會過多的消耗內(nèi)存;所以,緩存對象就要在整個(gè)應(yīng)用中使用。如果以前你用過Backbone.js開發(fā)過應(yīng)用,也許你會問你自己,“ 我要重取數(shù)據(jù)該怎么做?”你可以每次在如下路徑中觸發(fā)后重取數(shù)據(jù):
// Inside a router initialize: function() { this.cached = { view: undefined, model: undefined } }, index: function(parameter) { this.cached.model = this.cached.model || new Model({ parameter: parameter }); this.cached.view = this.cached.view || new View({ model: this.cached.model }); this.cached.model.fetch(); }
當(dāng)你的應(yīng)用從端點(diǎn)(如,一個(gè)收件箱)必須檢索最新數(shù)據(jù)時(shí)上面的模式就可以工作。當(dāng)然,如果你要拿的數(shù)據(jù)時(shí)憑借應(yīng)用的某個(gè)狀態(tài)(假設(shè)這個(gè)狀態(tài)是通過URL和參數(shù)來決定的),甚至是在用戶上一個(gè)頁面應(yīng)用的狀態(tài)沒有改變, 你可以重取數(shù)據(jù)。一個(gè)好的解決方案去重拿數(shù)據(jù)時(shí)當(dāng)應(yīng)用(參數(shù))發(fā)生變化時(shí):
// Inside a router initialize: function() { this.cached = { view: undefined, model: undefined } }, index: function(parameter) { this.cached.model = this.cached.model || new Model({ parameter:parameter }); this.cached.model.set('parameter', parameter); this.cached.view = this.cached.view || new View({ model: this.cached.model }); } // Inside of the model initialize: function() { this.on("change:parameter", this.fetchData, this); }
JSDoc函數(shù)和Backbone.js類
我是文檔注釋和JSDoc的超級粉絲。我用JSDoc對所有的Backbone類添加了文檔注釋:
var Thing = Backbone.View.extend(/** @lends Thing.prototype */{ /** @class Thing * @author Phillip Whisenhunt * @augments Backbone.View * @contructs Thing object */ initialize() {}, /** Gets data by ID from the thing. If the thing doesn't have data based on the ID, an empty string is returned. * @param {String} id The id of get data for. * @return {String} The data. */ getDataById: function(id) {} });
如果你對Backbone類進(jìn)行如上添加文檔注釋,這樣你可以給所有類和函數(shù) 添加參數(shù)、返回類型以及描述文檔注釋了。確保保持初始化函數(shù)作為一個(gè)聲明的函數(shù),這樣可以幫助我們生成JSDoc。如果你想看看JSDoc的例子工程,那就在 HomeAway Calendar Widget下載例子。同時(shí)這里也有個(gè) Grunt.js插件, grunt-jsdoc-plugin,這個(gè)也可以作為你構(gòu)建文檔注釋時(shí)的一部分。
聯(lián)系測試驅(qū)動的開發(fā)模式
我認(rèn)為如果你用Backbone.js,你應(yīng)該在開發(fā)模型和集合時(shí)遵循測試驅(qū)動開發(fā)(TDD)。我第一次用Jasmine.js創(chuàng)建模型和集合時(shí)遵循TDD進(jìn)行單元測試,但失敗了。一旦寫下單元測試并且失敗,我會對整個(gè)模型和集合進(jìn)行重寫。
通過這一點(diǎn),我的所有Jasmine測試都通過了,而且我有信心我的模型和集合會和我期望的一樣工作。自從我遵循TDD,我的視圖層非常容易寫而且非常簡單。當(dāng)你開始用TDD時(shí),你得速度當(dāng)然會很慢;但是一但你得腦海里一直想著TDD,你的編程效率和質(zhì)量會神奇般的提高。
相關(guān)文章
講解JavaScript的Backbone.js框架的MVC結(jié)構(gòu)設(shè)計(jì)理念
這篇文章主要介紹了JavaScript的Backbone.js框架的MVC結(jié)構(gòu)設(shè)計(jì)理念,相比較于Angular.js,同樣為MVC結(jié)構(gòu)的Backbone則顯得輕巧許多,需要的朋友可以參考下2016-02-02簡單了解Backbone.js的Model模型以及View視圖的源碼
這篇文章主要簡單介紹了Backbone.js的Model模型以及View視圖的源碼,Backbone是一款高人氣JavaScript的MVC框架,需要的朋友可以參考下2016-02-02backbone簡介_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了backbone簡介,詳細(xì)的介紹了backbone簡介和用法,有興趣的可以了解一下2017-07-07Backbone.js框架中Model與Collection的使用實(shí)例
這篇文章主要介紹了Backbone.js框架中Model與Collection的使用實(shí)例,Collection是Model的一個(gè)有序的集合,需要的朋友可以參考下2016-05-05Backbone.js框架中簡單的View視圖編寫學(xué)習(xí)筆記
這篇文章主要介紹了Backbone.js框架中簡單的View編寫學(xué)習(xí)筆記,Backbone是JavaScript的一款高人氣MVC框架,需要的朋友可以參考下2016-02-02關(guān)于backbone url請求中參數(shù)帶有中文存入數(shù)據(jù)庫是亂碼的快速解決辦法
這篇文章主要介紹了關(guān)于backbone url請求中參數(shù)帶有中文存入數(shù)據(jù)庫是亂碼的快速解決辦法的相關(guān)資料,需要的朋友可以參考下2016-06-06