因此,在這篇文章中,我們將介紹很多不同的設(shè)計(jì)模式供你在Backbone.js應(yīng)用中使用,而且我們也會(huì)一同來看看對(duì)于開發(fā)者來說會(huì)產(chǎn)生很多普遍的有關(guān)性能伸縮的問題。
對(duì)象深度拷貝
JavaScript對(duì)待所有原生類型變量是傳值。所以,當(dāng)變量被引用時(shí)就傳遞了變量的值。
舉個(gè)例子,上面的代碼將變量helloWorldCopy的值設(shè)置為變量helloWorld的值。這樣, 自從它的值被復(fù)制之后,所有修改helloWorldCopy的值不會(huì)修改helloWorld的值。JavaScript對(duì)待所有非原始類型的變量時(shí)傳引用,這就意味著當(dāng)變量傳遞的時(shí)候?qū)?huì)傳遞內(nèi)存地址引用。
舉個(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模型:
這樣你就創(chuàng)建了一個(gè)新的person對(duì)象:
現(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)證一下地址屬性。
現(xiàn)在,讓我們?cè)噲D給地址屬性設(shè)置一個(gè)不正確的ZIP代碼。
這將會(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è)深度的克隆方法
我們現(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。
現(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ì)像下面這樣。
我們僅僅更新了一個(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ì)派上用場(chǎng)。假設(shè)你有一個(gè)集合。
當(dāng)使用者點(diǎn)擊其中一個(gè)項(xiàng)目時(shí),這個(gè)項(xiàng)目成為了被選中狀態(tài)并且對(duì)于使用者作為選中項(xiàng)目是通過 aselectedclass 添加的是可視化的。以下這是一種方式:
這個(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)如下:
現(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ù)。
你也許能想象的到,這個(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)的樣子:
這種模式可以戲劇性的減少路由的過分膨脹。然而,需要注意到它不會(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è)概念形象化了。
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。
頁面加載時(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語言中,我采用下面方法之一:
應(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)證為失敗,就像下面這樣:
這個(gè)模式的優(yōu)點(diǎn)在于,你是在一個(gè)地方處理所有不合法的消息。缺點(diǎn)在于,如果你以不同的方式處理不合法的屬性的話,你的invalid方法可能會(huì)成為一個(gè)很大的switch或者if語句。
廣播自定義Error事件
我的一個(gè)朋友,Derick Bailey,推薦了一個(gè)可替代模式,就是為每個(gè)模型屬性觸發(fā)自定義的errors事件。這將允許你的視圖能夠針對(duì)單獨(dú)的屬性綁定到特定的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ù)文件。或者從你的IDE獲得相應(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的視圖:
上面的代碼首先會(huì)檢查是否已在視圖代碼中創(chuàng)建了指定視圖in-page-alert div。如果沒有,則接著查看一般性的在其它地方聲明的body-alert div。這樣可以讓你發(fā)送具有一致性的錯(cuò)誤信息以及當(dāng)你忘記指定一個(gè)in-page-alert div時(shí)提供有用且可靠的信息。如下面的模式簡化了讓你怎樣在你的試圖中處理錯(cuò)誤信息:
單頁面應(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)題。
單頁面應(yīng)用中的緩存對(duì)象
當(dāng)我們談?wù)搯雾撁鎽?yīng)用時(shí),另一個(gè)叫緩存對(duì)象模式你將會(huì)經(jīng)常用到!下面的例子直截了當(dāng)而且簡單:
這個(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ù):
當(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í):
JSDoc函數(shù)和Backbone.js類
我是文檔注釋和JSDoc的超級(jí)粉絲。我用JSDoc對(duì)所有的Backbone類添加了文檔注釋:
如果你對(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)系測(cè)試驅(qū)動(dòng)的開發(fā)模式
我認(rèn)為如果你用Backbone.js,你應(yīng)該在開發(fā)模型和集合時(shí)遵循測(cè)試驅(qū)動(dòng)開發(fā)(TDD)。我第一次用Jasmine.js創(chuàng)建模型和集合時(shí)遵循TDD進(jìn)行單元測(cè)試,但失敗了。一旦寫下單元測(cè)試并且失敗,我會(huì)對(duì)整個(gè)模型和集合進(jìn)行重寫。
通過這一點(diǎn),我的所有Jasmine測(cè)試都通過了,而且我有信心我的模型和集合會(huì)和我期望的一樣工作。自從我遵循TDD,我的視圖層非常容易寫而且非常簡單。當(dāng)你開始用TDD時(shí),你得速度當(dāng)然會(huì)很慢;但是一但你得腦海里一直想著TDD,你的編程效率和質(zhì)量會(huì)神奇般的提高。
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com