因此,在這篇文章中,我們將介紹很多不同的設(shè)計模式供你在Backbone.js應(yīng)用中使用,而且我們也會一同來看看對于開發(fā)者來說會產(chǎn)生很多普遍的有關(guān)性能伸縮的問題。
對象深度拷貝
JavaScript對待所有原生類型變量是傳值。所以,當(dāng)變量被引用時就傳遞了變量的值。
舉個例子,上面的代碼將變量helloWorldCopy的值設(shè)置為變量helloWorld的值。這樣, 自從它的值被復(fù)制之后,所有修改helloWorldCopy的值不會修改helloWorld的值。JavaScript對待所有非原始類型的變量時傳引用,這就意味著當(dāng)變量傳遞的時候?qū)鬟f內(nèi)存地址引用。
舉個例子,上面的代碼將設(shè)置helloWorldCopy為helloWorld的引用,而且,也許你會猜到任何修改helooWorldCopy的值都會直接導(dǎo)致helloWorld值的變化。如果你想要helloWorld的拷貝,你可以創(chuàng)建一個拷貝對象即可。
也許你會想到“為什么Backbone.js可以解釋為所有的工作都是通過傳遞引用?”事實上,Backbone.js不會拷貝對象,這將意味著如果你從模型里調(diào)用.get()方法獲得一個對象,任何給這個對象的修改都會直接修改原來的對象。讓我們一起來看一個例子來闡明哪里會發(fā)生這樣的情況。如果你有個如下的Person模型:
這樣你就創(chuàng)建了一個新的person對象:
現(xiàn)在我們來對新對象的一些屬性進行操作操作:
person.set('name', 'Phillip W.', { validate: true });
上面的代碼成功的給person對象的name屬性賦了值。現(xiàn)在我們在來操作person對象的地址屬性。當(dāng)然,在我們這樣做之前先驗證一下地址屬性。
現(xiàn)在,讓我們試圖給地址屬性設(shè)置一個不正確的ZIP代碼。
這將會怎樣呢?我們的驗證出現(xiàn)了錯誤!為什么屬性依舊被改變了?前邊我們說過,Backbone.js不會拷貝模型屬性;它會返回你所請求的一切。這樣,你也許會猜到,如果你需要一個對象,你將得到這個對象的引用,對這個對象的任何操作都會直接改變模型里的對象。如果你要debug,這可能將把你帶入到無底的兔子黑洞。
這個問題對于新的Backbone.js使用要引起注意,甚至對于老練的JavaScript程序員有時也會沒有提防。這個問題在GitHub的Backbone.js討論組中有很激烈的討論。正如Jeremy Ashkenas指出,執(zhí)行一個深的對象引用是個很難解決的難題,一個很深的對象引用是要花費很大代價的。
幸運的,jQuery 提供了一個深度拷貝功能來實現(xiàn),$.extend. 如同, Underscore.js ,一個Backbone.js的依靠,提供_.extend 方法,但是我必須避免使用它,因為它沒有執(zhí)行一份是個深度的復(fù)制,Lo-Dash, Underscore.js的一個分叉版本,提供了對象一個深度克隆的_.clone 方法的選項。然而,我使用 $.extend 方法的模型使用的語法規(guī)則去執(zhí)行一個任意對象的深度克隆。記得通過后,結(jié)果它執(zhí)行的是一個深度的克隆方法
我們現(xiàn)在快速準(zhǔn)確的復(fù)制一個theaddressobject?,并且我們能夠更改它對于我們要點沒有包括在內(nèi)的我們不用擔(dān)心會更改它原有的模型。你必須要意思到這個父工廠對于上面所有的事例因為它所有的地址對象成員都是不可變的(numbers, strings, etc.),與此同時這上面所有的事例工廠當(dāng)你要深度復(fù)制對象里面包含的對象時你都必須小心的使用。你必須也要知道一個小小的性能影響都來自于執(zhí)行一個深度的克隆,但是我從來沒有看到過很明顯的問題。然而,如果你要深度的克隆一個大對象或者成千上萬的對象所有的立即復(fù)制,你將有可能做大量的性能分析。這將領(lǐng)導(dǎo)我們直接到下一個模式。
為對象創(chuàng)建外觀
在現(xiàn)實世界中,需求經(jīng)常變化,JavaScript對象符號(或者說JSON)也是一樣,這些對象是由模型和集合所在的端點返回的。這或許會成為你的基礎(chǔ)代碼中的一個真正的大麻煩,如果你的視圖與底層的數(shù)據(jù)模型是緊耦合的話。因此,我為所有對象創(chuàng)建了getters和setters。
支持這個模式的人非常多。如果任何底層的數(shù)據(jù)結(jié)構(gòu)改變了,那么視圖層并不需要更新許多;你將有一個數(shù)據(jù)的訪問點,所以你不太可能忘記做一個深度拷貝,你的代碼將會更易于維護更易于調(diào)試。負(fù)面因素在于這個模式可能導(dǎo)致模型或集合的一點點膨脹。
我們看一個例子來闡明這個模式。想像我們有一個Hotel模型,包含有rooms和目前可獲得的rooms,而且我們希望可以通過床位大小來獲得rooms。
現(xiàn)在我們假設(shè)明天你就要發(fā)布你的代碼,而你又發(fā)現(xiàn)端點開發(fā)者忘記告訴你rooms的數(shù)據(jù)結(jié)構(gòu)改變了,由一個對象變?yōu)橐粋€數(shù)組。你的代碼現(xiàn)在看起來會像下面這樣。
我們僅僅更新了一個函數(shù),以便將Hotel的結(jié)構(gòu)轉(zhuǎn)變?yōu)檫@個應(yīng)用的其余部分所期望的結(jié)構(gòu),同時整個應(yīng)用仍然像我們所期待的一樣運作。如果這里沒有一個getter,我們很可能不得不為rooms更新每個訪問點。理想情況下,你會希望更新所有的函數(shù),以適應(yīng)新的數(shù)據(jù)結(jié)構(gòu),但如果你在時間方面有壓力急于發(fā)布的話,這個模式將可以拯救你。
離題說一句,這個模式既可以被認(rèn)為是裝飾模式,因為它隱藏了創(chuàng)建對象拷貝的復(fù)雜性,也可以認(rèn)為是橋接模式,因為它可以用來將數(shù)據(jù)轉(zhuǎn)換為所期望的形式。一個好的經(jīng)驗是對任何對象元素使用getters 和setters 。
存儲數(shù)據(jù)不是通過服務(wù)器保存
盡管Backbone.js有模型和集合映射的規(guī)定去具象狀態(tài)的傳輸(or REST-ful)的端點,你將花大量的時間去找你想要的存儲數(shù)據(jù)在你的模型或者不是在服務(wù)器上的連接。另外一些關(guān)于Backbone.js的文章,例如“Backbone.js Tips: Lessons From the Trenches” 是通過SupportBee的Prateek Dayal ,這個模式還有其他的描述。讓我們一起來快速的看一個小例子來幫助我們說明它可能會派上用場。假設(shè)你有一個集合。
當(dāng)使用者點擊其中一個項目時,這個項目成為了被選中狀態(tài)并且對于使用者作為選中項目是通過 aselectedclass 添加的是可視化的。以下這是一種方式:
這個模式也執(zhí)行了單一職責(zé)原則,規(guī)定了每個類應(yīng)該具有一個單一的職責(zé),而且它的職責(zé)應(yīng)該封裝與這個類之中,因為模型與集合要處理數(shù)據(jù),而視圖要處理渲染。
路由中的參數(shù)
最好的演示這個模式工作方式是舉個例子。比如說你需要對搜索頁面進行排序,每個搜索頁面都允許用戶添加兩個不同的過濾類型foo和bar,每個類型代表不同的觀點。
因此,你的URL結(jié)構(gòu)將會呈現(xiàn)如下:
現(xiàn)在,所有的路由都用的是同一個試圖和模型,這樣大多數(shù)人喜歡用同一個函數(shù)search()來實現(xiàn)。然而,你要是檢查過Backbone.js代碼的話,你會發(fā)祥它里面沒有排序的參數(shù)映射;這些參數(shù)只是從左至右依次傳入函數(shù)。這樣,為了都能統(tǒng)一使用一個函數(shù),你就要停止創(chuàng)建不同的函數(shù)正確的來為search()匹配參數(shù)。
你也許能想象的到,這個模式可以使路由功能很快膨脹。當(dāng)我第一次遇到這個問題時,我試圖創(chuàng)建了一些用正則表達(dá)式定義的解析函數(shù)來“神奇”的去匹配參數(shù),當(dāng)然這個是可以工作的-但這也是有約束條件的。這樣,我廢棄了這個想法(有時,我仍然可以用Backbone插件來解決)。我進入GitHub中的一個 議題,其中Ashkenas建議應(yīng)該讓所有的參數(shù)都和search函數(shù)匹配。
上面的代碼現(xiàn)在轉(zhuǎn)變?yōu)橄旅婢S護性更強的樣子:
這種模式可以戲劇性的減少路由的過分膨脹。然而,需要注意到它不會服務(wù)于不能區(qū)別的參數(shù)。比如,如果你有兩個作為ID的參數(shù),如模式XXXX-XXXX,你不能區(qū)分哪個ID是對哪個參數(shù)的回應(yīng)。
model.fetch() 不會清除你的模型
這通常會將那些Backbone.js的新手給絆倒:model.fetch()并不能丟掉你的模型,而是擴展了你的模型的屬性。因此,如果你的模型具有屬性x,y和z,你獲取到y(tǒng)和z,那么x將仍然是模型中的那個x,只有y和z會被更新。下面的例子將這個概念形象化了。
PUTs 需要一個 ID 屬性
這一條也經(jīng)常將Backbone.js的新手絆倒。要想在調(diào)用.save()的時候讓模型發(fā)送一個HTTP PUT請求,你的模型需要有一個ID屬性集。記得HTTP PUT謂詞是設(shè)計來做更新的吧,所以發(fā)送一個PUT請求,你的模型需要有一個ID,這么做是有意義的。在理想的世界里,你的所有模型都具有一個名為ID的完美的ID屬性,但是你從端點接收到的JSON數(shù)據(jù)可能并不總是具有完美命名的IDs。
因此,如果你需要更新一個模型,請在保存之前確認(rèn)模型上有ID。Backbone.js 的0.5以及更高版本允許你用id屬性來更新模型的ID屬性名稱,如果你的端點返回的不是名為id的IDs的話。
如果困頓于使用的是版本低于0.5的Backbone.js,我建議你修改你的模型或集合的parse函數(shù),以便將你期望的ID屬性映射到屬性ID。這里有一個快速上手的例子,說明了你應(yīng)怎樣修改parse函數(shù)來做到這一點。我們假設(shè)你有一個cars的集合,它的IDs是carID。
頁面加載時創(chuàng)建模型數(shù)據(jù)
有時你會發(fā)現(xiàn)你的模型或者集合需要在頁面加載時被初始化賦值。許多關(guān)于Backbone.js模式的文章,例如Rico Sta Cruz的 “Backbone 模式” 和 Katz的 “ 避免常見的Backbone.js陷阱” ,討論了這種模式。這種模式實現(xiàn)很容易,只需在頁面中內(nèi)聯(lián)一段腳本,通過你選擇的服務(wù)端語言,將單個模型屬性或者JSON形式的數(shù)據(jù)呈現(xiàn)出來。例如,在Rails語言中,我采用下面方法之一:
應(yīng)用這種模式可以通過“立即的”渲染頁面,改善你的搜索引擎排名,而且它也可以通過限制應(yīng)用初始化HTTP請求的方式,大大縮短你的應(yīng)用啟動與運行所需要的時間。
處理失敗的模型屬性驗證
很多時候,你會想知道是哪個模型屬性驗證失敗了。例如,如果你有一個極其復(fù)雜的表單,你或許想知道哪個模型屬性驗證失敗,這樣你就可以將這個屬性對應(yīng)的輸入字段高亮顯示。不幸的是,提醒視圖到底是哪個模型屬性驗證失敗并沒有直接集成于Backbone.js,但是你可以用一些不同的模式去處理這個問題。
返回一個錯誤對象一個給視圖提醒哪個模型屬性驗證失敗的模式是,返回一個對象,其中包含某種標(biāo)志,它詳細(xì)的記錄了哪個屬性驗證為失敗,就像下面這樣:
這個模式的優(yōu)點在于,你是在一個地方處理所有不合法的消息。缺點在于,如果你以不同的方式處理不合法的屬性的話,你的invalid方法可能會成為一個很大的switch或者if語句。
廣播自定義Error事件
我的一個朋友,Derick Bailey,推薦了一個可替代模式,就是為每個模型屬性觸發(fā)自定義的errors事件。這將允許你的視圖能夠針對單獨的屬性綁定到特定的error事件:
這個模式的優(yōu)點在于,你的視圖明確的綁定到它們所綁定到的error類型,而且如果你對每一種屬性error有特定的指令的話,它可以清理你的視圖部分代碼,使之更易于維護。這個模式的一個不好的地方在于,如果在你處理不同的屬性error時并沒有太多的不同的話,你的視圖可能會變得極為膨脹。
這兩種模式都有其利弊,你應(yīng)該考慮清楚哪個模式對你的應(yīng)用案例是最優(yōu)的。如果你按照同樣的方式處理所有失敗的驗證,那么第一個方法可能是最好的;如果你對每個模型屬性有特定的UI變化,那么后一種方法更好。
HTTP狀態(tài)代碼200所觸發(fā)的錯誤
如果你的瀏覽器端模型或者集合收到了無效的JSON,盡管HTTP的狀態(tài)代碼是200,但瀏覽器端依然會觸發(fā)一個“錯誤”事件。這種事件常發(fā)生于本地模擬JSON數(shù)據(jù)造成的。那么,一個好的方法就是讀取經(jīng)過 JSON 驗證器驗證了的模擬JSON數(shù)據(jù)文件。或者從你的IDE獲得相應(yīng)的 插件來及時獲取格式錯誤的JSON信息。創(chuàng)建一個一般性錯誤顯示模式
創(chuàng)建一個常見錯誤顯示代碼可以節(jié)省你的時間以及創(chuàng)建一個統(tǒng)一的模式來處理、可視化錯誤信息,而且它可以增加開發(fā)者的經(jīng)驗。我之前開發(fā)的每一個Backbone.js應(yīng)用中我都會創(chuàng)建一個可以處理alert的視圖:
上面的代碼首先會檢查是否已在視圖代碼中創(chuàng)建了指定視圖in-page-alert div。如果沒有,則接著查看一般性的在其它地方聲明的body-alert div。這樣可以讓你發(fā)送具有一致性的錯誤信息以及當(dāng)你忘記指定一個in-page-alert div時提供有用且可靠的信息。如下面的模式簡化了讓你怎樣在你的試圖中處理錯誤信息:
單頁面應(yīng)用中更新瀏覽器頁面標(biāo)題
這是一個比任何東西都重要的可用性問題。如果你正在開發(fā)一個單頁面應(yīng)用程序,謹(jǐn)記更新每個頁面的標(biāo)題。我寫過一個的插件(Backbone.js Router Title Helper)來擴展 backbone.js router 的功能。它通過一個 Map 對象來控制路由,鍵來代表路由函數(shù)的名字,值則映射到頁面的標(biāo)題。
單頁面應(yīng)用中的緩存對象
當(dāng)我們談?wù)搯雾撁鎽?yīng)用時,另一個叫緩存對象模式你將會經(jīng)常用到!下面的例子直截了當(dāng)而且簡單:
這個模式可以加速你得應(yīng)用,因為你不用重復(fù)初始化你得Backbone.js對象。然而,它會過多的消耗內(nèi)存;所以,緩存對象就要在整個應(yīng)用中使用。如果以前你用過Backbone.js開發(fā)過應(yīng)用,也許你會問你自己,“ 我要重取數(shù)據(jù)該怎么做?”你可以每次在如下路徑中觸發(fā)后重取數(shù)據(jù):
當(dāng)你的應(yīng)用從端點(如,一個收件箱)必須檢索最新數(shù)據(jù)時上面的模式就可以工作。當(dāng)然,如果你要拿的數(shù)據(jù)時憑借應(yīng)用的某個狀態(tài)(假設(shè)這個狀態(tài)是通過URL和參數(shù)來決定的),甚至是在用戶上一個頁面應(yīng)用的狀態(tài)沒有改變, 你可以重取數(shù)據(jù)。一個好的解決方案去重拿數(shù)據(jù)時當(dāng)應(yīng)用(參數(shù))發(fā)生變化時:
JSDoc函數(shù)和Backbone.js類
我是文檔注釋和JSDoc的超級粉絲。我用JSDoc對所有的Backbone類添加了文檔注釋:
如果你對Backbone類進行如上添加文檔注釋,這樣你可以給所有類和函數(shù) 添加參數(shù)、返回類型以及描述文檔注釋了。確保保持初始化函數(shù)作為一個聲明的函數(shù),這樣可以幫助我們生成JSDoc。如果你想看看JSDoc的例子工程,那就在 HomeAway Calendar Widget下載例子。同時這里也有個 Grunt.js插件, grunt-jsdoc-plugin,這個也可以作為你構(gòu)建文檔注釋時的一部分。
聯(lián)系測試驅(qū)動的開發(fā)模式
我認(rèn)為如果你用Backbone.js,你應(yīng)該在開發(fā)模型和集合時遵循測試驅(qū)動開發(fā)(TDD)。我第一次用Jasmine.js創(chuàng)建模型和集合時遵循TDD進行單元測試,但失敗了。一旦寫下單元測試并且失敗,我會對整個模型和集合進行重寫。
通過這一點,我的所有Jasmine測試都通過了,而且我有信心我的模型和集合會和我期望的一樣工作。自從我遵循TDD,我的視圖層非常容易寫而且非常簡單。當(dāng)你開始用TDD時,你得速度當(dāng)然會很慢;但是一但你得腦海里一直想著TDD,你的編程效率和質(zhì)量會神奇般的提高。
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com