您好,登錄后才能下訂單哦!
簡(jiǎn)介
Web 應(yīng)用程序越來越關(guān)注于前端,使用客戶端腳本與 Ajax 進(jìn)行交互。由于 JavaScript 應(yīng)用程序越來越復(fù)雜,如果沒有合適的工具和模式,那么 JavaScript 代碼的高效編寫、非重復(fù)性和可維護(hù)性方面會(huì)面臨挑戰(zhàn)。模型-視圖-控制器 (MVC) 是一個(gè)常見模式,可用于服務(wù)器端開發(fā)以生成有組織以及易維護(hù)的代碼。MVC 支持將數(shù)據(jù)(比如通常用于 Ajax 交互的 JavaScript Object Notation (JSON) 對(duì)象)從表示層或從頁面的文檔對(duì)象模型 (document object model, DOM) 中分離出來,也可適用于客戶端開發(fā)。
Backbone(也稱為 Backbone.js)是由 Jeremy Ashkenas 創(chuàng)建的一個(gè)輕量級(jí)庫,可用于創(chuàng)建 MVC 類應(yīng)用程序。Backbone:
模型、視圖、集合和路由器是 Backbone 框架中的主要組件。在 Backbone 中,模型會(huì)存儲(chǔ)通過 RESTful JSON 接口從服務(wù)器檢索到的數(shù)據(jù)。模型與視圖密切關(guān)聯(lián),負(fù)責(zé)為特定 UI 組件渲染 HTML 并處理元素上觸發(fā)的事件,這也是視圖本身的一部分。
SPI 應(yīng)用程序:Backbone.Router 和 Backbone.history
含有大量 Ajax 交互的應(yīng)用程序越來越像那些無頁面刷新的應(yīng)用程序。這些應(yīng)用程序常常試圖限制與單個(gè)頁面的交互。該 SPI 方法提高了效率和速度,并使整個(gè)應(yīng)用程序變得更靈敏。狀態(tài)概念代替了頁面概念。散列 (Hash) 片段被用于識(shí)別一個(gè)特定狀態(tài)。散列片段 是 URL 中散列標(biāo)簽 (#) 后的那部分,是該類應(yīng)用程序的關(guān)鍵元素。清單 1 顯示了一個(gè) SPI 應(yīng)用程序使用兩個(gè)不同的散列片段產(chǎn)生的兩個(gè)不同狀態(tài)。
清單 1. SPI 或 Ajax 應(yīng)用程序中的兩個(gè)不同狀態(tài)
http://www.example.com/#/state1
http://www.example.com/#/state2
Backbone 提供一個(gè)稱為路由器(版本 0.5 前稱之為控制器)的組件來路由客戶端狀態(tài)。路由器可以擴(kuò)展 Backbone.Router 函數(shù),且包含一個(gè)散列映射(routes 屬性)將狀態(tài)與活動(dòng)關(guān)聯(lián)起來。當(dāng)應(yīng)用程序達(dá)到相關(guān)狀態(tài)時(shí),會(huì)觸發(fā)一個(gè)特定活動(dòng)。清單2 展示了一個(gè) Backbone 路由器示例。
清單 2. Backbone.Router 示例:routers.js
App.Routers.Main = Backbone.Router.extend({ // Hash maps for routes routes : { "" : "index", "/teams" : "getTeams", "/teams/:country" : "getTeamsCountry", "/teams/:country/:name : "getTeam" "*error" : "fourOfour" }, index: function(){ // Homepage }, getTeams: function() { // List all teams }, getTeamsCountry: function(country) { // Get list of teams for specific country }, getTeam: function(country, name) { // Get the teams for a specific country and with a specific name }, fourOfour: function(error) { // 404 page } });
創(chuàng)建的每個(gè)狀態(tài)可以為書簽。當(dāng) URL 碰到類似下面情況時(shí),會(huì)調(diào)用這 5 個(gè)活動(dòng)(index、getTeams、getTeamsCountry、getTeamCountry 和 fourOfour)。
要啟動(dòng) Backbone,先實(shí)例化頁面加載的路由器,并通過指令 Backbone.history.start() 方法監(jiān)視散列片段中的任何變更,如 清單 3 所示。
清單 3. 應(yīng)用程序?qū)嵗ㄊ褂?jQuery)
$(function(){ var router = new App.Routers.Main(); Backbone.history.start({pushState : true}); })
當(dāng)實(shí)例化路由器時(shí),會(huì)生成 Backbone.history 對(duì)象;它將自動(dòng)引用 Backbone.History 函數(shù)。Backbone.History 負(fù)責(zé)匹配路由和router 對(duì)象中定義的活動(dòng)。start() 方法觸發(fā)后,將創(chuàng)建 Backbone.history 的 fragment 屬性。它包含散列片段的值。該序列在根據(jù)狀態(tài)次序管理瀏覽器歷史方面十分有用。用戶如果想要返回前一狀態(tài),單擊瀏覽器的返回按鈕。
在 清單 3 的示例中,通過一個(gè)啟用 HTML5 特性 pushState 的配置調(diào)用 start() 方法。對(duì)于那些支持 pushState 的瀏覽器,Backbone 將監(jiān)視 popstate 事件以觸發(fā)一個(gè)新狀態(tài)。如果瀏覽器不能支持 HTML5 特性,那么 onhashchange 活動(dòng)會(huì)被監(jiān)視。如果瀏覽器不支持該事件,輪詢技術(shù)將監(jiān)視 URL 散列片段的任何更改。
模型和集合
模型和集合是 Backbone.js 的重要組件,模型將數(shù)據(jù)(通常是來自服務(wù)器的數(shù)據(jù))存儲(chǔ)在鍵值對(duì)中。要?jiǎng)?chuàng)建一個(gè)模型,需要擴(kuò)展Backbone.Model,如 清單 4 所示。
清單 4. Backbone.Model 創(chuàng)建
App.Models.Team = Backbone.Model.extend({ defaults : { // default attributes } // Domain-specific methods go here });
App.Models.Team 函數(shù)是一個(gè)新模型函數(shù),但是必須創(chuàng)建一個(gè)實(shí)例才能在應(yīng)用程序中使用特定模型,如 清單 5 所示。
清單 5. 模型實(shí)例化
var team1 = new App.Models.Team();
現(xiàn)在,變量 team1 有一個(gè)名為 cid 的字段名,這是一個(gè)客戶端標(biāo)識(shí)符,形式為 "c" 再加上一個(gè)數(shù)字(例如,c0、c1、c2)。模型是通過存儲(chǔ)在散列映射中的屬性來定義的。屬性可以在實(shí)例化時(shí)進(jìn)行設(shè)置,或者使用 set() 方法設(shè)置。屬性值可通過 get() 方法檢索。清單 6 顯示了如何通過實(shí)例化或 get()/set() 方法設(shè)置和獲取屬性。
清單 6. 模型實(shí)例化和 get/set 方法
// "name" attribute is set into the model var team1 = new App.Models.Team({ name : "name1" }); console.log(team1.get("name")); // prints "name1" // "name" attribute is set with a new value team1.set({ name : "name2" }); console.log(team1.get("name")); //prints "name2"
當(dāng)使用 JavaScript 對(duì)象時(shí),使用 set() 方法創(chuàng)建或者設(shè)置屬性值的原因并不是顯而易見的。其中一個(gè)原因是為了更新此值,如 清單 7 所示。
清單 7. 以錯(cuò)誤的方法更新屬性
team1.attributes.name = "name2";
為了避免 使用 清單 7 中的代碼,使用 set() 是改變模型狀態(tài)并觸發(fā)其變更事件的唯一方法。使用 set() 提升封裝原則。清單 8 展示了如何將一個(gè)事件處理程序綁到發(fā)生變更的事件中。該事件處理程序包含一個(gè) alert,在調(diào)用 set() 方法時(shí)會(huì)被觸發(fā),如 清單 6 所示。但是,在使用 清單 7 中的代碼時(shí)不觸發(fā) alert。
清單 8. 更改 App.Models.Team 模型中的事件處理程序
App.Models.Team = Backbone.Model.extend({ initialize : function(){ this.bind("change", this.changed); }, changed : function(){ alert("changed"); } });
Backbone 的另一個(gè)優(yōu)勢(shì)是易于通過 Ajax 交互與服務(wù)器進(jìn)行通信。在模型上調(diào)用一個(gè) save() 方法會(huì)通過 REST JSON API 異步將當(dāng)前狀態(tài)保存到服務(wù)器。清單 9 展示了此示例。
清單 9. 在模型對(duì)象上調(diào)用 save 方法
barca.save();
save() 函數(shù)將在后臺(tái)委托給 Backbone.sync,這是負(fù)責(zé)發(fā)出 RESTful 請(qǐng)求的組件,默認(rèn)使用 jQuery 函數(shù) $.ajax()。由于調(diào)用了 REST 風(fēng)格架構(gòu),每個(gè) Create、Read、Update 或 Delete (CRUD) 活動(dòng)均會(huì)與各種不同類型的 HTTP 請(qǐng)求(POST、GET、PUT 和 DELETE)相關(guān)聯(lián)。首先保存模型對(duì)象,使用一個(gè) POST 請(qǐng)求,創(chuàng)建一個(gè)標(biāo)識(shí)符 ID,其后,嘗試發(fā)送對(duì)象到服務(wù)器,使用一個(gè) PUT 請(qǐng)求。
當(dāng)需要從服務(wù)器檢索一個(gè)模型時(shí),請(qǐng)求一個(gè) Read 活動(dòng)并使用一個(gè) Ajax GET 請(qǐng)求。這類請(qǐng)求使用 fetch() 方法。要確定導(dǎo)入模型數(shù)據(jù)或者從中取出模型數(shù)據(jù)的服務(wù)器的位置:
清單 10 顯示了如何獲取一個(gè)模型。
清單 10. 模型對(duì)象的 Fetch() 方法
var teamNew = new App.Models.Team({ urlRoot : '/specialTeams' }); teamNew.save(); // returns model's ID equal to '222' teamNew.fetch(); // Ajax request to '/specialTeams/222'
validate() 方法被用于驗(yàn)證模型,如 清單 11 所示。需要重寫 validate() 方法(在調(diào)用 set() 方法時(shí)觸發(fā))來包含模型的有效邏輯。傳遞給該函數(shù)的惟一參數(shù)是一個(gè) JavaScript 對(duì)象,該對(duì)象包含了 set() 方法更新的屬性,以便驗(yàn)證那些屬性的條件。如果從 validate() 方法中沒有返回任何內(nèi)容,那么驗(yàn)證成功。如果返回一個(gè)錯(cuò)誤消息,那么驗(yàn)證失敗,將無法執(zhí)行 set() 方法。
清單 11. 模型的驗(yàn)證方法
App.Models.Team = Backbone.Model.extend({ validate : function(attributes){ if (!!attributes && attributes.name === "teamX") { // Error message returned if the value of the "name" // attribute is equal to "teamX" return "Error!"; } } }
一組模型被分組到到集合中,這個(gè)集合是 Backbone.Collection 的擴(kuò)展函數(shù)。集合具有一個(gè)模型屬性的特性,定義了組成該集合的模型類型。使用 add()/remove() 方法可以將一個(gè)模型添加和移動(dòng)到集合中。清單 12 顯示了如何創(chuàng)建和填充一個(gè)集合。
清單 12. Backbone 集合
App.Collections.Teams = Backbone.Collection.extend({ model : App.Models.Team }); var teams = new App.Collections.Teams(); // Add e model to the collection object "teams" teams.add(team1); teams.add(new App.Models.Team({ name : "Team B" })); teams.add(new App.Models.Team()); teams.remove(team1); console.log(teams.length) // prints 2
創(chuàng)建的 teams 集合中包含一個(gè)含有兩個(gè)模型的陣列,存儲(chǔ)在模型屬性中。盡管,在典型 Ajax 應(yīng)用程序中,會(huì)從服務(wù)器動(dòng)態(tài)(不是人工)填充該集合。fetch() 方法可以幫助完成此項(xiàng)任務(wù),如 清單 13 所示,并將數(shù)據(jù)存儲(chǔ)到模型陣列中。
清單 13. Fetch() 方法
teams.fetch();
Backbone 中的集合擁有一個(gè) url 屬性,定義了使用 Ajax GET 請(qǐng)求從服務(wù)器取出 JSON 數(shù)據(jù)的位置,如 清單 14 所示。
清單 14. 集合的 url 屬性和 fetch() 方法
teams.url = '/getTeams'; teams.fetch(); //Ajax GET Request to '/getTeams'
Fetch() 方法屬于異步調(diào)用,因此,在等待服務(wù)器響應(yīng)時(shí),應(yīng)用程序不會(huì)中止。在一些情況下,要操作來自服務(wù)器的原始數(shù)據(jù),可以使用集合的 parse() 方法。正如 清單 15 所示。
清單 15. parse() 方法
App.Collections.Teams = Backbone.Collection.extend({ model : App.Models.Team, parse : function(data) { // 'data' contains the raw JSON object console.log(data); } });
集合提供的另一個(gè)有趣的方法是 reset(),它允許將多個(gè)模型設(shè)置到一個(gè)集合中。reset() 方法可以非常方便地將數(shù)據(jù)引導(dǎo)到集合中,比如頁面加載,來避免用戶等待異步調(diào)用返回。
視圖和客戶端模板
Backbone 中的視圖與典型 MVC 方法的視圖不一樣。Backbone 視圖可以擴(kuò)展 Backbone.View 函數(shù)并顯示模型中存儲(chǔ)的數(shù)據(jù)。一個(gè)視圖提供一個(gè)由 el 屬性定義的 HTML 元素。該屬性可以是由 tagName、className 和 id 屬性相組合而構(gòu)成的,或者是通過其本身的 el 值形成的。清單 16 顯示了使用這不同方法組合 el 屬性的兩個(gè)不同視圖。
清單 16. Backbone 視圖樣例
// In the following view, el value is 'UL.team-element' App.Views.Teams = Backbone.View.extend({ el : 'UL.team-list' }); // In the following view, el value is 'div.team-element' App.Views.Team = Backbone.View.extend({ className : '.team-element', tagName : 'div' });
如果 el、tagName、className 和 id 屬性為空,那么會(huì)默認(rèn)將一個(gè)空的 DIV 分配給 el。
如上所述,一個(gè)視圖必須與一個(gè)模型相關(guān)聯(lián)。該模型屬性也很有用,如 清單 17 所示。App.View.Team 視圖被綁定到一個(gè)App.Models.Team 模型實(shí)例。
清單 17. Backbone 視圖中的模型屬性
// In the following view, el value is 'UL.team-element' App.Views.Team = Backbone.View.extend({ ... model : new App.Models.Team });
要渲染數(shù)據(jù)(這是視圖的主要目的),重寫 render() 方法和邏輯來顯示 DOM 元素(由 el 屬性引用的)中的模型屬性。清單 18 展示了一個(gè) render 方法如何更新用戶界面的樣例。
清單 18. Render() 方法
App.Views.Team = Backbone.View.extend({ className : '.team-element', tagName : 'div', model : new App.Models.Team render : function() { // Render the 'name' attribute of the model associated // inside the DOM element referred by 'el' $(this.el).html("<span>" + this.model.get("name") + "</span>"); } });
Backbone 也可以促進(jìn)客戶端模板的使用,這就使得我們沒有必要在 JavaScript 中嵌入 HTML 代碼,如 清單 18 所示。(使用模板,模板會(huì)封裝視圖中常見函數(shù);只指定此函數(shù)一次即可。)Backbone 在 underscore.js(一個(gè)必須的庫)中提供一個(gè)模板引擎,盡管沒有必要使用該模板引擎。清單 19 中的實(shí)例使用 underscore.js HTML 模板。
清單 19. HTML 含有模板
<script id="teamTemplate" type="text/template"> <%= name %> </script>
清單 20 顯示了另一個(gè)使用 underscore.js HTML 模板的樣例。
清單 20. 使用 _.template() 函數(shù)的視圖
App.Views.Team = Backbone.View.extend({ className : '.team-element', tagName : 'div', model : new App.Models.Team render : function() { // Compile the template var compiledTemplate = _.template($('#teamTemplate').html()); // Model attributes loaded into the template. Template is // appended to the DOM element referred by the el attribute $(this.el).html(compiledTemplate(this.model.toJSON())); } });
Backbone 中最有用且最有趣的一個(gè)功能是將 render() 方法綁定到模型的變更事件中,如 清單 21 所示。
清單 21. Render() 方法綁定到模型變更事件
// In the following view, el value is 'div.team-element' App.Views.Team = Backbone.View.extend({ model : new App.Models.Team, initialize : function() { this.model.bind("change", this.render, this); } })
上述代碼將 render() 方法綁定到一個(gè)模型的變更事件中。當(dāng)模型發(fā)生更改時(shí),會(huì)自動(dòng)觸發(fā) render() 方法,從而節(jié)省數(shù)行代碼。從 Backbone 0.5.2 開始,bind() 方法就開始接受使用第三個(gè)參數(shù)來定義回調(diào)函數(shù)的對(duì)象。(在上述示例中,當(dāng)前視圖是回調(diào)函數(shù) render() 中的對(duì)象)。在 Backbone 0.5.2 之前的版本中,必須使用 underscore.js 中的 bindAll 函數(shù),如 清單 22 所示。
清單 22. _.bindAll() usage
// In the following view, el value is 'div.team-element' App.Views.Team = Backbone.View.extend({ initialize : function() { _.bindAll(this, "render"); this.model.bind("change", this.render); } })
Backbone 視圖中,通過視圖中的 DOM 對(duì)象監(jiān)聽事件是比較容易的。對(duì)于實(shí)現(xiàn)這一點(diǎn),events 屬性很是方便的,如 清單 23 所示。
清單 23. 事件屬性
App.Views.Team = Backbone.View.extend({ className : '.team-element', tagName : 'div', events : { "click a.more" : "moreInfo" }, moreInfo : function(e){ // Logic here } })
events 屬性的每個(gè)項(xiàng)均由兩部分構(gòu)成:
在 清單 23 中,當(dāng)用戶通過 DIV 中的類 more 以及類 team-element 點(diǎn)擊鏈接時(shí),會(huì)調(diào)用函數(shù) moreInfo
結(jié)束語
MVC 模式可以為大型 JavaScript 應(yīng)用程序提供所需的組織化代碼。Backbone 是一個(gè) JavaScript MVC 框架,它屬于輕量級(jí)框架,且易于學(xué)習(xí)掌握。模型、視圖、集合和路由器從不同的層面劃分了應(yīng)用程序,并負(fù)責(zé)處理幾種特定事件。處理 Ajax 應(yīng)用程序或者 SPI 應(yīng)用程序時(shí),Backbone 可能是最好的解決方案。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。