简介
Web 应用程序越来越关注于前端,使用客户端脚本与 Ajax 进行交互。由于 JavaScript 应用程序越来越复杂,如果没有合适的工具和模式,那么 JavaScript 代码的高效编写、非重复性和可维护性方面会面临挑战。模型-视图-控制器 (MVC) 是一个常见模式,可用于服务器端开发以生成有组织以及易维护的代码。MVC 支持将数据(比如通常用于 Ajax 交互的 JavaScript Object Notation (JSON) 对象)从表示层或从页面的文档对象模型 (document object model,DOM) 中分离出来,也可适用于客户端开发。
Backbone(也称为 Backbone.js)是由Jeremy Ashkenas创建的一个轻量级库,可用于创建 MVC 类应用程序。Backbone:
- 强制依赖于 Underscore.js,Underscore.js 是一个实用型库
- 非强制依赖于 jQuery/Zepto
- 根据模型的变更自动更新应用程序的 HTML,有助于代码维护
- 促进客户端模板使用,避免了在 JavaScript 中嵌入 HTML 代码
模型、视图、集合和路由器是 Backbone 框架中的主要组件。在 Backbone 中,模型会存储通过 RESTful JSON 接口从服务器检索到的数据。模型与视图密切关联,负责为特定 UI 组件渲染 HTML 并处理元素上触发的事件,这也是视图本身的一部分。
SPI 应用程序:Backbone.Router和Backbone.history
含有大量 Ajax 交互的应用程序越来越像那些无页面刷新的应用程序。这些应用程序常常试图限制与单个页面的交互。该 SPI 方法提高了效率和速度,并使整个应用程序变得更灵敏。状态概念代替了页面概念。散列 (Hash) 片段被用于识别一个特定状态。散列片段是 URL 中散列标签 (#) 后的那部分,是该类应用程序的关键元素。清单 1显示了一个 SPI 应用程序使用两个不同的散列片段产生的两个不同状态。
清单 1. SPI 或 Ajax 应用程序中的两个不同状态
http://www.example.com/#/state1
http://www.example.com/#/state2
Backbone 提供一个称为路由器(版本 0.5 前称之为控制器)的组件来路由客户端状态。路由器可以扩展Backbone.Router函数,且包含一个散列映射(routes属性)将状态与活动关联起来。当应用程序达到相关状态时,会触发一个特定活动。清单2展示了一个 Backbone 路由器示例。
清单 2.Backbone.Router示例:routers.js
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所示。
清单 3. 应用程序实例化(使用 jQuery)
当实例化路由器时,会生成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所示。
清单 4.Backbone.Model创建
App.Models.Team函数是一个新模型函数,但是必须创建一个实例才能在应用程序中使用特定模型,如清单 5所示。
清单 5. 模型实例化
现在,变量team1有一个名为 cid 的字段名,这是一个客户端标识符,形式为 "c" 再加上一个数字(例如,c0、c1、c2)。模型是通过存储在散列映射中的属性来定义的。属性可以在实例化时进行设置,或者使用set()方法设置。属性值可通过get()方法检索。清单 6显示了如何通过实例化或get()/set()方法设置和获取属性。
清单 6. 模型实例化和 get/set 方法
team1.set({
name : "name2"
});
console.log(team1.get("name")); //prints "name2"
当使用 JavaScript 对象时,使用set()方法创建或者设置属性值的原因并不是显而易见的。其中一个原因是为了更新此值,如清单 7所示。
清单 7. 以错误的方法更新属性
为了避免使用清单 7中的代码,使用set()是改变模型状态并触发其变更事件的唯一方法。使用set()提升封装原则。清单 8展示了如何将一个事件处理程序绑到发生变更的事件中。该事件处理程序包含一个 alert,在调用set()方法时会被触发,如清单 6所示。但是,在使用清单 7中的代码时不触发 alert。
清单 8. 更改 App.Models.Team 模型中的事件处理程序
Backbone 的另一个优势是易于通过 Ajax 交互与服务器进行通信。在模型上调用一个save()方法会通过 REST JSON API 异步将当前状态保存到服务器。清单 9展示了此示例。
清单 9. 在模型对象上调用save方法
save()函数将在后台委托给Backbone.sync,这是负责发出 RESTful 请求的组件,默认使用 jQuery 函数$.ajax()。由于调用了 REST 风格架构,每个 Create、Read、Update 或 Delete (CRUD) 活动均会与各种不同类型的 HTTP 请求(POST、GET、PUT和DELETE)相关联。首先@R_531_301@对象,使用一个POST请求,创建一个标识符 ID,其后,尝试发送对象到服务器,使用一个PUT请求。
当需要从服务器检索一个模型时,请求一个 Read 活动并使用一个 AjaxGET请求。这类请求使用fetch()方法。要确定导入模型数据或者从中取出模型数据的服务器的位置:
- 如果模型属于一个collection,那么集合对象的url属性将是该位置的基础,并且该模型 ID(不是 cid)会被附加以构成完整的 URL。
- 如果模型不是在一个集合中,那么该模型的urlroot属性被用作该位置的基础
清单 10. 模型对象的Fetch()方法
validate()方法被用于验证模型,如清单 11所示。需要重写validate()方法(在调用set()方法时触发)来包含模型的有效逻辑。传递给该函数的惟一参数是一个 JavaScript 对象,该对象包含了set()方法更新的属性,以便验证那些属性的条件。如果从validate()方法中没有返回任何内容,那么验证成功。如果返回一个错误消息,那么验证失败,将无法执行set()方法。
清单 11. 模型的验证方法
一组模型被分组到到集合中,这个集合是Backbone.Collection的扩展函数。集合具有一个模型属性的特性,定义了组成该集合的模型类型。使用add()/remove()方法可以将一个模型添加和移动到集合中。清单 12显示了如何创建和填充一个集合。
清单 12. Backbone 集合
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
创建的teams集合中包含一个含有两个模型的阵列,存储在模型属性中。尽管,在典型 Ajax 应用程序中,会从服务器动态(不是人工)填充该集合。fetch()方法可以帮助完成此项任务,如清单 13所示,并将数据存储到模型阵列中。
清单 13.Fetch()方法
Backbone 中的集合拥有一个url属性,定义了使用 Ajax GET 请求从服务器取出 JSON 数据的位置,如清单 14所示。
清单 14. 集合的url属性和fetch()方法
Fetch()方法属于异步调用,因此,在等待服务器响应时,应用程序不会中止。在一些情况下,要操作来自服务器的原始数据,可以使用集合的parse()方法。正如清单 15所示。
清单 15.parse()方法
集合提供的另一个有趣的方法是reset(),它允许将多个模型设置到一个集合中。reset()方法可以非常方便地将数据引导到集合中,比如页面加载,来避免用户等待异步调用返回。
视图和客户端模板
Backbone 中的视图与典型 MVC 方法的视图不一样。Backbone 视图可以扩展Backbone.View函数并显示模型中存储的数据。一个视图提供一个由el属性定义的 HTML 元素。该属性可以是由tagName、className和id属性相组合而构成的,或者是通过其本身的el值形成的。清单 16显示了使用这不同方法组合el属性的两个不同视图。
清单 16. Backbone 视图样例
如果el、tagName、className和id属性为空,那么会默认将一个空的 DIV 分配给el。
如上所述,一个视图必须与一个模型相关联。该模型属性也很有用,如清单 17所示。App.View.Team视图被绑定到一个App.Models.Team模型实例。
清单 17. Backbone 视图中的模型属性
要渲染数据(这是视图的主要目的),重写render()方法和逻辑来显示 DOM 元素(由el属性引用的)中的模型属性。清单 18展示了一个 render 方法如何更新用户界面的样例。
清单 18.Render()方法
Backbone 也可以促进客户端模板的使用,这就使得我们没有必要在 JavaScript 中嵌入 HTML 代码,如清单 18所示。(使用模板,模板会封装视图中常见函数;只指定此函数一次即可。)Backbone 在 underscore.js(一个必须的库)中提供一个模板引擎,尽管没有必要使用该模板引擎。清单 19中的实例使用 underscore.js HTML 模板。
清单 19. HTML 含有模板
清单 20显示了另一个使用 underscore.js HTML 模板的样例。
清单 20. 使用_.template()函数的视图
Backbone 中最有用且最有趣的一个功能是将render()方法绑定到模型的变更事件中,如清单 21所示。
清单 21.Render()方法绑定到模型变更事件
上述代码将render()方法绑定到一个模型的变更事件中。当模型发生更改时,会自动触发render()方法,从而节省数行代码。从 Backbone 0.5.2 开始,bind()方法就开始接受使用第三个参数来定义回调函数的对象。(在上述示例中,当前视图是回调函数render()中的对象)。在 Backbone 0.5.2 之前的版本中,必须使用 underscore.js 中的bindAll函数,如清单 22所示。
清单 22._.bindAll()usage
Backbone 视图中,通过视图中的 DOM 对象监听事件是比较容易的。对于实现这一点,events属性很是方便的,如清单 23所示。
清单 23. 事件属性
events属性的每个项均由两部分构成:
- 左边部分指定事件类型和触发事件的选择器。
- 右边部分定义了事件处理函数。
在清单 23中,当用户通过 DIV 中的类more以及类team-element点击链接时,会调用函数moreInfo
结束语
MVC 模式可以为大型 JavaScript 应用程序提供所需的组织化代码。Backbone 是一个 JavaScript MVC 框架,它属于轻量级框架,且易于学习掌握。模型、视图、集合和路由器从不同的层面划分了应用程序,并负责处理几种特定事件。处理 Ajax 应用程序或者 SPI 应用程序时,Backbone 可能是最好的解决方案。
原文链接:https://www.f2er.com/js/37854.html