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í)還會(huì)產(chǎn)生很多普遍的問題的。
因此,在這篇文章中,我們將介紹很多不同的設(shè)計(jì)模式供你在Backbone.js應(yīng)用中使用,而且我們也會(huì)一同來看看對(duì)于開發(fā)者來說會(huì)產(chǎn)生很多普遍的有關(guān)性能伸縮的問題。
對(duì)象深度拷貝
JavaScript對(duì)待所有原生類型變量是傳值。所以,當(dāng)變量被引用時(shí)就傳遞了變量的值。
var helloWorld = “Hello World”; var helloWorldCopy = helloWorld;
舉個(gè)例子,上面的代碼將變量helloWorldCopy的值設(shè)置為變量helloWorld的值。這樣, 自從它的值被復(fù)制之后,所有修改helloWorldCopy的值不會(huì)修改helloWorld的值。JavaScript對(duì)待所有非原始類型的變量時(shí)傳引用,這就意味著當(dāng)變量傳遞的時(shí)候?qū)?huì)傳遞內(nèi)存地址引用。
var helloWorld = {
‘hello': ‘world'
}
var helloWorldCopy = helloWorld;
舉個(gè)例子,上面的代碼將設(shè)置helloWorldCopy為helloWorld的引用,而且,也許你會(huì)猜到任何修改helooWorldCopy的值都會(huì)直接導(dǎo)致helloWorld值的變化。如果你想要helloWorld的拷貝,你可以創(chuàng)建一個(gè)拷貝對(duì)象即可。
也許你會(huì)想到“為什么Backbone.js可以解釋為所有的工作都是通過傳遞引用?”事實(shí)上,Backbone.js不會(huì)拷貝對(duì)象,這將意味著如果你從模型里調(diào)用.get()方法獲得一個(gè)對(duì)象,任何給這個(gè)對(duì)象的修改都會(huì)直接修改原來的對(duì)象。讓我們一起來看一個(gè)例子來闡明哪里會(huì)發(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對(duì)象:
var person = new Person({
'name': 'Phillip W'
});
現(xiàn)在我們來對(duì)新對(duì)象的一些屬性進(jìn)行操作操作:
person.set('name', 'Phillip W.', { validate: true });
上面的代碼成功的給person對(duì)象的name屬性賦了值?,F(xiàn)在我們?cè)趤聿僮鱬erson對(duì)象的地址屬性。當(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)在,讓我們?cè)噲D給地址屬性設(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'
}
*/
這將會(huì)怎樣呢?我們的驗(yàn)證出現(xiàn)了錯(cuò)誤!為什么屬性依舊被改變了?前邊我們說過,Backbone.js不會(huì)拷貝模型屬性;它會(huì)返回你所請(qǐng)求的一切。這樣,你也許會(huì)猜到,如果你需要一個(gè)對(duì)象,你將得到這個(gè)對(duì)象的引用,對(duì)這個(gè)對(duì)象的任何操作都會(huì)直接改變模型里的對(duì)象。如果你要debug,這可能將把你帶入到無底的兔子黑洞。
這個(gè)問題對(duì)于新的Backbone.js使用要引起注意,甚至對(duì)于老練的JavaScript程序員有時(shí)也會(huì)沒有提防。這個(gè)問題在GitHub的Backbone.js討論組中有很激烈的討論。正如Jeremy Ashkenas指出,執(zhí)行一個(gè)深的對(duì)象引用是個(gè)很難解決的難題,一個(gè)很深的對(duì)象引用是要花費(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è)分叉版本,提供了對(duì)象一個(gè)深度克隆的_.clone 方法的選項(xiàng)。然而,我使用 $.extend 方法的模型使用的語法規(guī)則去執(zhí)行一個(gè)任意對(duì)象的深度克隆。記得通過后,結(jié)果它執(zhí)行的是一個(gè)深度的克隆方法
var address = $.extend(true, {}, person.address);
我們現(xiàn)在快速準(zhǔn)確的復(fù)制一個(gè)theaddressobject?,并且我們能夠更改它對(duì)于我們要點(diǎn)沒有包括在內(nèi)的我們不用擔(dān)心會(huì)更改它原有的模型。你必須要意思到這個(gè)父工廠對(duì)于上面所有的事例因?yàn)樗械牡刂穼?duì)象成員都是不可變的(numbers, strings, etc.),與此同時(shí)這上面所有的事例工廠當(dāng)你要深度復(fù)制對(duì)象里面包含的對(duì)象時(shí)你都必須小心的使用。你必須也要知道一個(gè)小小的性能影響都來自于執(zhí)行一個(gè)深度的克隆,但是我從來沒有看到過很明顯的問題。然而,如果你要深度的克隆一個(gè)大對(duì)象或者成千上萬的對(duì)象所有的立即復(fù)制,你將有可能做大量的性能分析。這將領(lǐng)導(dǎo)我們直接到下一個(gè)模式。
為對(duì)象創(chuàng)建外觀
在現(xiàn)實(shí)世界中,需求經(jīng)常變化,JavaScript對(duì)象符號(hào)(或者說JSON)也是一樣,這些對(duì)象是由模型和集合所在的端點(diǎn)返回的。這或許會(huì)成為你的基礎(chǔ)代碼中的一個(gè)真正的大麻煩,如果你的視圖與底層的數(shù)據(jù)模型是緊耦合的話。因此,我為所有對(duì)象創(chuàng)建了getters和setters。
支持這個(gè)模式的人非常多。如果任何底層的數(shù)據(jù)結(jié)構(gòu)改變了,那么視圖層并不需要更新許多;你將有一個(gè)數(shù)據(jù)的訪問點(diǎn),所以你不太可能忘記做一個(gè)深度拷貝,你的代碼將會(huì)更易于維護(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è)對(duì)象變?yōu)橐粋€(gè)數(shù)組。你的代碼現(xiàn)在看起來會(huì)像下面這樣。
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)。理想情況下,你會(huì)希望更新所有的函數(shù),以適應(yīng)新的數(shù)據(jù)結(jié)構(gòu),但如果你在時(shí)間方面有壓力急于發(fā)布的話,這個(gè)模式將可以拯救你。
離題說一句,這個(gè)模式既可以被認(rèn)為是裝飾模式,因?yàn)樗[藏了創(chuàng)建對(duì)象拷貝的復(fù)雜性,也可以認(rèn)為是橋接模式,因?yàn)樗梢杂脕韺?shù)據(jù)轉(zhuǎn)換為所期望的形式。一個(gè)好的經(jīng)驗(yàn)是對(duì)任何對(duì)象元素使用getters 和setters 。
存儲(chǔ)數(shù)據(jù)不是通過服務(wù)器保存
盡管Backbone.js有模型和集合映射的規(guī)定去具象狀態(tài)的傳輸(or REST-ful)的端點(diǎn),你將花大量的時(shí)間去找你想要的存儲(chǔ)數(shù)據(jù)在你的模型或者不是在服務(wù)器上的連接。另外一些關(guān)于Backbone.js的文章,例如“Backbone.js Tips: Lessons From the Trenches” 是通過SupportBee的Prateek Dayal ,這個(gè)模式還有其他的描述。讓我們一起來快速的看一個(gè)小例子來幫助我們說明它可能會(huì)派上用場。假設(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)并且對(duì)于使用者作為選中項(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)目,并且我們沒有必要通過對(duì)象模型去判斷。這種模式對(duì)于存儲(chǔ)無用的數(shù)據(jù)是非常有用的以至于 你可能非常想要去跟蹤;請(qǐng)記住你能夠創(chuàng)建一個(gè)模型并且沒有必要去關(guān)聯(lián)于他們存儲(chǔ)的一些無用的圖像數(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)被選中,并且我們沒有必要通過這些對(duì)象模型來了解。這個(gè)模式對(duì)于存儲(chǔ)無用的數(shù)據(jù)是非常有用的,請(qǐng)記住,您可以創(chuàng)建不一定有端點(diǎn)相關(guān)聯(lián)的存儲(chǔ)無關(guān)的視圖數(shù)據(jù)的模型和集合。
這種模式的缺點(diǎn)是你存儲(chǔ)了無用的數(shù)據(jù)在你的模型或者集合中,它們不能真正意義上的追隨一個(gè)平靜的架構(gòu)是因?yàn)樗鼈儾粫?huì)完美的去映射在web資源上;另外,這個(gè)模式會(huì)引起一些很膨脹的在你的模型中;;并且當(dāng)你保存你的模型的時(shí)候如果你的端點(diǎn)嚴(yán)格的只接受JSON數(shù)據(jù)它會(huì)引起一個(gè)很大的煩惱。
你可能會(huì)問你自己,“我如何確定我是否應(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),我可能會(huì)增加這種邏輯模型。總而言之,大多數(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'));
}
});
在這里,任何對(duì)模型的改變都會(huì)觸發(fā)對(duì)視圖的一個(gè)全面的重新渲染。我第一次用Backbone.js開發(fā)時(shí),我是這個(gè)模式的實(shí)踐者。但隨著視圖代碼的增長,我迅速的意識(shí)到,這種方法不利于維護(hù)或優(yōu)化,因?yàn)楫?dāng)模型的任何一個(gè)屬性發(fā)生變化時(shí),視圖將會(huì)完全的重新渲染。
當(dāng)我遇到這個(gè)問題,我迅速的用Google搜索了一下,看看別人是怎么做的,結(jié)果找到了Ian Storm Taylor的博客,“分解你的Backbone.js渲染方法”,他在其中描述了在模型中監(jiān)聽單獨(dú)的屬性變化,然后僅僅重新渲染相對(duì)于變化屬性的視圖部分。Taylor也描述了返回對(duì)象的引用,以便單獨(dú)的渲染函數(shù)可以很容易的鏈接在一起。上面的例子現(xiàn)在現(xiàn)在就變得更易于維護(hù),性能更優(yōu)。因?yàn)槲覀儍H僅更新了模型變化的屬性相對(duì)應(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,提供了模型屬性與視圖元素的鍵-值綁定,這會(huì)讓你省去編寫許多樣板代碼,如果你具有復(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)將會(huì)幫助你預(yù)防意大利面條式的代碼,而沒有人喜歡意大利面條式的代碼!
保持你的數(shù)據(jù)層徹底的與視圖層無關(guān),這將會(huì)使你創(chuàng)建出更具模塊化,可復(fù)用與可維護(hù)的基礎(chǔ)代碼。你可以非常容易的在應(yīng)用程序各個(gè)地方復(fù)用與擴(kuò)展模型和集合,而不需要考慮它們所綁定的視圖。遵循這個(gè)模式使對(duì)你項(xiàng)目不熟悉的開發(fā)者能迅速的深入到基礎(chǔ)代碼之中,因?yàn)樗麄儠?huì)確切的知道哪里發(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è)例子。比如說你需要對(duì)搜索頁面進(jìn)行排序,每個(gè)搜索頁面都允許用戶添加兩個(gè)不同的過濾類型foo和bar,每個(gè)類型代表不同的觀點(diǎn)。
因此,你的URL結(jié)構(gòu)將會(huì)呈現(xiàn)如下:
'search/:foo' 'search/:bar' 'search/:foo/:bar'
現(xiàn)在,所有的路由都用的是同一個(gè)試圖和模型,這樣大多數(shù)人喜歡用同一個(gè)函數(shù)search()來實(shí)現(xiàn)。然而,你要是檢查過Backbone.js代碼的話,你會(huì)發(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);
},
這種模式可以戲劇性的減少路由的過分膨脹。然而,需要注意到它不會(huì)服務(wù)于不能區(qū)別的參數(shù)。比如,如果你有兩個(gè)作為ID的參數(shù),如模式XXXX-XXXX,你不能區(qū)分哪個(gè)ID是對(duì)哪個(gè)參數(shù)的回應(yīng)。
model.fetch() 不會(huì)清除你的模型
這通常會(huì)將那些Backbone.js的新手給絆倒:model.fetch()并不能丟掉你的模型,而是擴(kuò)展了你的模型的屬性。因此,如果你的模型具有屬性x,y和z,你獲取到y(tǒng)和z,那么x將仍然是模型中的那個(gè)x,只有y和z會(huì)被更新。下面的例子將這個(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請(qǐng)求,你的模型需要有一個(gè)ID屬性集。記得HTTP PUT謂詞是設(shè)計(jì)來做更新的吧,所以發(fā)送一個(gè)PUT請(qǐng)求,你的模型需要有一個(gè)ID,這么做是有意義的。在理想的世界里,你的所有模型都具有一個(gè)名為ID的完美的ID屬性,但是你從端點(diǎn)接收到的JSON數(shù)據(jù)可能并不總是具有完美命名的IDs。
因此,如果你需要更新一個(gè)模型,請(qǐng)?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í)你會(huì)發(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請(qǐng)求的方式,大大縮短你的應(yīng)用啟動(dòng)與運(yùn)行所需要的時(shí)間。
處理失敗的模型屬性驗(yàn)證
很多時(shí)候,你會(huì)想知道是哪個(gè)模型屬性驗(yàn)證失敗了。例如,如果你有一個(gè)極其復(fù)雜的表單,你或許想知道哪個(gè)模型屬性驗(yàn)證失敗,這樣你就可以將這個(gè)屬性對(duì)應(yīng)的輸入字段高亮顯示。不幸的是,提醒視圖到底是哪個(gè)模型屬性驗(yàn)證失敗并沒有直接集成于Backbone.js,但是你可以用一些不同的模式去處理這個(gè)問題。
返回一個(gè)錯(cuò)誤對(duì)象
一個(gè)給視圖提醒哪個(gè)模型屬性驗(yàn)證失敗的模式是,返回一個(gè)對(duì)象,其中包含某種標(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方法可能會(huì)成為一個(gè)很大的switch或者if語句。
廣播自定義Error事件
我的一個(gè)朋友,Derick Bailey,推薦了一個(gè)可替代模式,就是為每個(gè)模型屬性觸發(fā)自定義的errors事件。這將允許你的視圖能夠針對(duì)單獨(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類型,而且如果你對(duì)每一種屬性error有特定的指令的話,它可以清理你的視圖部分代碼,使之更易于維護(hù)。這個(gè)模式的一個(gè)不好的地方在于,如果在你處理不同的屬性error時(shí)并沒有太多的不同的話,你的視圖可能會(huì)變得極為膨脹。
這兩種模式都有其利弊,你應(yīng)該考慮清楚哪個(gè)模式對(duì)你的應(yīng)用案例是最優(yōu)的。如果你按照同樣的方式處理所有失敗的驗(yàn)證,那么第一個(gè)方法可能是最好的;如果你對(duì)每個(gè)模型屬性有特定的UI變化,那么后一種方法更好。
HTTP狀態(tài)代碼200所觸發(fā)的錯(cuò)誤
如果你的瀏覽器端模型或者集合收到了無效的JSON,盡管HTTP的狀態(tài)代碼是200,但瀏覽器端依然會(huì)觸發(fā)一個(gè)“錯(cuò)誤”事件。這種事件常發(fā)生于本地模擬JSON數(shù)據(jù)造成的。那么,一個(gè)好的方法就是讀取經(jīng)過 JSON 驗(yàn)證器驗(yàn)證了的模擬JSON數(shù)據(jù)文件?;蛘邚哪愕腎DE獲得相應(yīng)的 插件來及時(shí)獲取格式錯(cuò)誤的JSON信息。
創(chuàng)建一個(gè)一般性錯(cuò)誤顯示模式
創(chuàng)建一個(gè)常見錯(cuò)誤顯示代碼可以節(jié)省你的時(shí)間以及創(chuàng)建一個(gè)統(tǒng)一的模式來處理、可視化錯(cuò)誤信息,而且它可以增加開發(fā)者的經(jīng)驗(yàn)。我之前開發(fā)的每一個(gè)Backbone.js應(yīng)用中我都會(huì)創(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();
}
});
上面的代碼首先會(huì)檢查是否已在視圖代碼中創(chuàng)建了指定視圖in-page-alert div。如果沒有,則接著查看一般性的在其它地方聲明的body-alert div。這樣可以讓你發(fā)送具有一致性的錯(cuò)誤信息以及當(dāng)你忘記指定一個(gè)in-page-alert div時(shí)提供有用且可靠的信息。如下面的模式簡化了讓你怎樣在你的試圖中處理錯(cuò)誤信息:
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 對(duì)象來控制路由,鍵來代表路由函數(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)用中的緩存對(duì)象
當(dāng)我們談?wù)搯雾撁鎽?yīng)用時(shí),另一個(gè)叫緩存對(duì)象模式你將會(huì)經(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對(duì)象。然而,它會(huì)過多的消耗內(nèi)存;所以,緩存對(duì)象就要在整個(gè)應(yīng)用中使用。如果以前你用過Backbone.js開發(fā)過應(yīng)用,也許你會(huì)問你自己,“ 我要重取數(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的超級(jí)粉絲。我用JSDoc對(duì)所有的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) {}
});
如果你對(duì)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ū)動(dòng)的開發(fā)模式
我認(rèn)為如果你用Backbone.js,你應(yīng)該在開發(fā)模型和集合時(shí)遵循測試驅(qū)動(dòng)開發(fā)(TDD)。我第一次用Jasmine.js創(chuàng)建模型和集合時(shí)遵循TDD進(jìn)行單元測試,但失敗了。一旦寫下單元測試并且失敗,我會(huì)對(duì)整個(gè)模型和集合進(jìn)行重寫。
通過這一點(diǎn),我的所有Jasmine測試都通過了,而且我有信心我的模型和集合會(huì)和我期望的一樣工作。自從我遵循TDD,我的視圖層非常容易寫而且非常簡單。當(dāng)你開始用TDD時(shí),你得速度當(dāng)然會(huì)很慢;但是一但你得腦海里一直想著TDD,你的編程效率和質(zhì)量會(huì)神奇般的提高。
相關(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
輕量級(jí)javascript 框架Backbone使用指南
這篇文章主要介紹了輕量級(jí)javascript 框架Backbone使用指南的相關(guān)資料,需要的朋友可以參考下2015-07-07
簡單了解Backbone.js的Model模型以及View視圖的源碼
這篇文章主要簡單介紹了Backbone.js的Model模型以及View視圖的源碼,Backbone是一款高人氣JavaScript的MVC框架,需要的朋友可以參考下2016-02-02
backbone簡介_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了backbone簡介,詳細(xì)的介紹了backbone簡介和用法,有興趣的可以了解一下2017-07-07
Backbone.js框架中Model與Collection的使用實(shí)例
這篇文章主要介紹了Backbone.js框架中Model與Collection的使用實(shí)例,Collection是Model的一個(gè)有序的集合,需要的朋友可以參考下2016-05-05
Backbone.js框架中簡單的View視圖編寫學(xué)習(xí)筆記
這篇文章主要介紹了Backbone.js框架中簡單的View編寫學(xué)習(xí)筆記,Backbone是JavaScript的一款高人氣MVC框架,需要的朋友可以參考下2016-02-02
關(guān)于backbone url請(qǐng)求中參數(shù)帶有中文存入數(shù)據(jù)庫是亂碼的快速解決辦法
這篇文章主要介紹了關(guān)于backbone url請(qǐng)求中參數(shù)帶有中文存入數(shù)據(jù)庫是亂碼的快速解決辦法的相關(guān)資料,需要的朋友可以參考下2016-06-06

