如何将模型-视图-控制器 (MVC) 架构引入 Ajax Web 应用程序
如何高效管理 Web 应用程序中的数目众多的 javaScript 代码行是一个挑战。Asynchronous Javascript and xml (Ajax) 交互大量充斥着各种页面,为用户提供了更好体验。越来越普遍的单页界面均由 Ajax 驱动。Backbone 是一个 JavaScript 框架,可用于创建模型-视图-控制器 (model-view-controller, MVC) 类应用程序和单页界面。在本文中,我们将了解 Backbone 如何用于创建 Ajax 应用程序或单页界面。
Web 应用程序越来越关注于前端,使用客户端脚本与 Ajax 进行交互。由于 JavaScript 应用程序越来越复杂,如果没有合适的工具和模式,那么 JavaScript 代码的高效编写、非重复性和可维护性方面会面临挑战。模型-视图-控制器 (MVC) 是一个常见模式,可用于服务器端开发以生成有组织以及易维护的代码。MVC 支持将数据(比如通常用于 Ajax 交互的 JavaScript Object Notation (JSON) 对象)从表示层或从页面的文档对象模型 (document object model, DOM) 中分离出来,也可适用于客户端开发。
Backbone(也称为 Backbone.js)是由Jeremy Ashkenas创建的一个轻量级库,可用于创建 MVC 类应用程序。Backbone:
模型、视图、集合和路由器是 Backbone 框架中的主要组件。在 Backbone 中,模型会存储通过 RESTful JSON 接口从服务器检索到的数据。模型与视图密切关联,负责为特定 UI 组件渲染 HTML 并处理元素上触发的事件,这也是视图本身的一部分。
在本文中,我们将学习几个不同的 Backbone.js 框架组件。研究 MVC 如何应用于 Backbone。通过这些实例,看看在创建 Ajax 应用程序或者单页界面 (SPI) 时 Backbone 是多么的重要。
下载本文使用的源代码。
回页首
Backbone.Router
和Backbone.history
含有大量 Ajax 交互的应用程序越来越像那些无页面刷新的应用程序。这些应用程序常常试图限制与单个页面的交互。该 SPI 方法提高了效率和速度,并使整个应用程序变得更灵敏。状态概念代替了页面概念。散列 (Hash) 片段被用于识别一个特定状态。散列片段是 URL 中散列标签 (#) 后的那部分,是该类应用程序的关键元素。清单 1显示了一个 SPI 应用程序使用两个不同的散列片段产生的两个不同状态。
http://www.example.com/#/state1
http://www.example.com/#/state2
Backbone 提供一个称为路由器(版本 0.5 前称之为控制器)的组件来路由客户端状态。路由器可以扩展Backbone.Router
函数,且包含一个散列映射(routes
属性)将状态与活动关联起来。当应用程序达到相关状态时,会触发一个特定活动。清单 2展示了一个 Backbone 路由器示例。
Backbone.Router
示例:routers.jsApp.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
}
});
创建的每个状态可以为书签。当 URL 碰到类似下面情况时,会调用这 5 个活动(index
、getTeams
、getTeamsCountry
、getTeamCountry
和fourOfour
)。
http://www.example.com
触发index()
http://www.example.com/#/teams
触发getTeams()
http://www.example.com/#/teams/country1
触发getTeamsCountry()
传递country1
作为参数http://www.example.com/#/teams/country1/team1
触发getTeamCountry()
传递country1
和team1
作为参数http://www.example.com/#/something
触发fourOfour()
以作*
(星号)使用。要启动 Backbone,先实例化页面加载的路由器,并通过指令Backbone.history.start()
方法监视散列片段中的任何变更,如清单 3所示。
$(function(){
var router = new App.Routers.Main();
Backbone.history.start({pushState : true});
})
当实例化路由器时,会生成Backbone.history
对象;它将自动引用Backbone.History
函数。Backbone.History
负责匹配路由和router
对象中定义的活动。start()
方法触发后,将创建Backbone.history
的fragment
属性。它包含散列片段的值。该序列在根据状态次序管理浏览器历史方面十分有用。用户如果想要返回前一状态,单击浏览器的返回按钮。
在清单 3的示例中,通过一个启用 HTML5 特性pushState
的配置调用start()
方法。对于那些支持pushState
的浏览器,Backbone 将监视 popstate 事件以触发一个新状态。如果浏览器不能支持 HTML5 特性,那么onhashchange
活动会被监视。如果浏览器不支持该事件,轮询技术将监视 URL 散列片段的任何更改。
回页首
模型和集合是 Backbone.js 的重要组件,模型将数据(通常是来自服务器的数据)存储在键值对中。要创建一个模型,需要扩展Backbone.Model
,如清单 4所示。
Backbone.Model
创建App.Models.Team = Backbone.Model.extend({
defaults : {
// default attributes
}
// Domain-specific methods go here
});
App.Models.Team
函数是一个新模型函数,但是必须创建一个实例才能在应用程序中使用特定模型,如清单 5所示。
var team1 = new App.Models.Team();
现在,变量team1
有一个名为 cid 的字段名,这是一个客户端标识符,形式为 "c" 再加上一个数字(例如,c0、c1、c2)。模型是通过存储在散列映射中的属性来定义的。属性可以在实例化时进行设置,或者使用set()
方法设置。属性值可通过get()
方法检索。清单 6显示了如何通过实例化或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"
当使用 JavaScript 对象时,使用set()
方法创建或者设置属性值的原因并不是显而易见的。其中一个原因是为了更新此值,如清单 7所示。
team1.attributes.name = "name2";
为了避免使用清单 7中的代码,使用set()
是改变模型状态并触发其变更事件的唯一方法。使用set()
提升封装原则。清单 8展示了如何将一个事件处理程序绑到发生变更的事件中。该事件处理程序包含一个 alert,在调用set()
方法时会被触发,如清单 6所示。但是,在使用清单 7中的代码时不触发 alert。
App.Models.Team = Backbone.Model.extend({
initialize : function(){
this.bind("change", this.changed);
},
changed : function(){
alert("changed");
}
});
Backbone 的另一个优势是易于通过 Ajax 交互与服务器进行通信。在模型上调用一个save()
方法会通过 REST JSON API 异步将当前状态保存到服务器。清单 9展示了此示例。
save
方法barca.save();
save()
函数将在后台委托给Backbone.sync
,这是负责发出 RESTful 请求的组件,默认使用 jQuery 函数$.ajax()
。由于调用了 REST 风格架构,每个 Create、Read、Update 或 Delete (CRUD) 活动均会与各种不同类型的 HTTP 请求(POST
、GET
、PUT
和DELETE
)相关联。首先保存模型对象,使用一个POST
请求,创建一个标识符 ID,其后,尝试发送对象到服务器,使用一个PUT
请求。
当需要从服务器检索一个模型时,请求一个 Read 活动并使用一个 AjaxGET
请求。这类请求使用fetch()
方法。要确定导入模型数据或者从中取出模型数据的服务器的位置:
url
属性将是该位置的基础,并且该模型 ID(不是 cid)会被附加以构成完整的 URL。urlroot
属性被用作该位置的基础清单 10显示了如何获取一个模型。
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()
方法被用于验证模型,如清单 11所示。需要重写validate()
方法(在调用set()
方法时触发)来包含模型的有效逻辑。传递给该函数的惟一参数是一个 JavaScript 对象,该对象包含了set()
方法更新的属性,以便验证那些属性的条件。如果从validate()
方法中没有返回任何内容,那么验证成功。如果返回一个错误消息,那么验证失败,将无法执行set()
方法。
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!";
}
}
}
一组模型被分组到到集合中,这个集合是Backbone.Collection
的扩展函数。集合具有一个模型属性的特性,
新闻热点
疑难解答