开始学习 Backbone

【转】开始学习 Backbone

如何将模型-视图-控制器 (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:

  • 强制依赖于 Underscore.js,Underscore.js 是一个实用型库
  • 非强制依赖于 jQuery/Zepto
  • 根据模型的变更自动更新应用程序的 HTML,有助于代码维护
  • 促进客户端模板使用,避免了在 JavaScript 中嵌入 HTML 代码

模型、视图、集合和路由器是 Backbone 框架中的主要组件。在 Backbone 中,模型会存储通过 RESTful JSON 接口从服务器检索到的数据。模型与视图密切关联,负责为特定 UI 组件渲染 HTML 并处理元素上触发的事件,这也是视图本身的一部分。

常用缩略词

  • DOM:文档对象模型
  • MVC:模型-视图-控制器
  • SPI:单页界面

在本文中,我们将学习几个不同的 Backbone.js 框架组件。研究 MVC 如何应用于 Backbone。通过这些实例,看看在创建 Ajax 应用程序或者单页界面 (SPI) 时 Backbone 是多么的重要。

下载 本文使用的源代码。

回页首

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
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
   }
});

创建的每个状态可以为书签。当 URL 碰到类似下面情况时,会调用这 5 个活动(indexgetTeamsgetTeamsCountrygetTeamCountry 和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)
$(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 所示。

清单 4. Backbone.Model 创建
App.Models.Team = Backbone.Model.extend({
    defaults : {
       // default attributes
    }
    // Domain-specific methods go here
});

App.Models.Team 函数是一个新模型函数,但是必须创建一个实例才能在应用程序中使用特定模型,如 清单 5 所示。

清单 5. 模型实例化
var team1 = new App.Models.Team();

现在,变量 team1 有一个名为 cid 的字段名,这是一个客户端标识符,形式为 "c" 再加上一个数字(例如,c0、c1、c2)。模型是通过存储在散列映射中的属性来定义的。属性可以在实例化时进行设置,或者使用 set() 方法设置。属性值可通过 get() 方法检索。清单 6 显示了如何通过实例化或 get()/set() 方法设置和获取属性。

清单 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 所示。

清单 7. 以错误的方法更新属性
team1.attributes.name = "name2";

为了避免 使用 清单 7 中的代码,使用 set() 是改变模型状态并触发其变更事件的唯一方法。使用 set() 提升封装原则。清单 8 展示了如何将一个事件处理程序绑到发生变更的事件中。该事件处理程序包含一个 alert,在调用 set() 方法时会被触发,如 清单 6 所示。但是,在使用 清单 7 中的代码时不触发 alert。

清单 8. 更改 App.Models.Team 模型中的事件处理程序
App.Models.Team = Backbone.Model.extend({
    initialize : function(){
        this.bind("change", this.changed);
    },
    changed : function(){
        alert("changed");
    }
});

Backbone 的另一个优势是易于通过 Ajax 交互与服务器进行通信。在模型上调用一个 save() 方法会通过 REST JSON API 异步将当前状态保存到服务器。清单 9 展示了此示例。

清单 9. 在模型对象上调用 save 方法
barca.save();

save() 函数将在后台委托给 Backbone.sync,这是负责发出 RESTful 请求的组件,默认使用 jQuery 函数 $.ajax()。由于调用了 REST 风格架构,每个 Create、Read、Update 或 Delete (CRUD) 活动均会与各种不同类型的 HTTP 请求(POSTGETPUT 和 DELETE)相关联。首先保存模型对象,使用一个 POST 请求,创建一个标识符 ID,其后,尝试发送对象到服务器,使用一个 PUT 请求。

当需要从服务器检索一个模型时,请求一个 Read 活动并使用一个 Ajax GET 请求。这类请求使用 fetch() 方法。要确定导入模型数据或者从中取出模型数据的服务器的位置:

  • 如果模型属于一个 collection,那么集合对象的 url 属性将是该位置的基础,并且该模型 ID(不是 cid)会被附加以构成完整的 URL。
  • 如果模型不是在一个集合中,那么该模型的 urlroot 属性被用作该位置的基础

清单 10 显示了如何获取一个模型。

清单 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() 方法。

清单 11. 模型的验证方法
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 的扩展函数。集合具有一个模型属性的特性,定义了组成该集合的模型类型。使用 add()/remove() 方法可以将一个模型添加和移动到集合中。清单 12 显示了如何创建和填充一个集合。

清单 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

创建的 teams 集合中包含一个含有两个模型的阵列,存储在模型属性中。尽管,在典型 Ajax 应用程序中,会从服务器动态(不是人工)填充该集合。fetch() 方法可以帮助完成此项任务,如 清单 13 所示,并将数据存储到模型阵列中。

清单 13. Fetch() 方法
teams.fetch();

Backbone 中的集合拥有一个 url 属性,定义了使用 Ajax GET 请求从服务器取出 JSON 数据的位置,如 清单 14 所示。

清单 14. 集合的 url 属性和 fetch() 方法
teams.url = ‘/getTeams‘;
teams.fetch(); //Ajax GET Request to ‘/getTeams‘

Fetch() 方法属于异步调用,因此,在等待服务器响应时,应用程序不会中止。在一些情况下,要操作来自服务器的原始数据,可以使用集合的 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);
    }
});

集合提供的另一个有趣的方法是 reset(),它允许将多个模型设置到一个集合中。reset() 方法可以非常方便地将数据引导到集合中,比如页面加载,来避免用户等待异步调用返回。

回页首

视图和客户端模板

Backbone 中的视图与典型 MVC 方法的视图不一样。Backbone 视图可以扩展 Backbone.View 函数并显示模型中存储的数据。一个视图提供一个由 el 属性定义的 HTML 元素。该属性可以是由 tagNameclassName 和 id 属性相组合而构成的,或者是通过其本身的 el 值形成的。清单 16 显示了使用这不同方法组合 el 属性的两个不同视图。

清单 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‘
});

如果 eltagNameclassName 和 id 属性为空,那么会默认将一个空的 DIV 分配给 el

如上所述,一个视图必须与一个模型相关联。该模型属性也很有用,如 清单 17 所示。App.View.Team 视图被绑定到一个 App.Models.Team模型实例。

清单 17. Backbone 视图中的模型属性
// In the following view, el value is ‘UL.team-element‘
App.Views.Team = Backbone.View.extend({
    ...
    model : new App.Models.Team
});

要渲染数据(这是视图的主要目的),重写 render() 方法和逻辑来显示 DOM 元素(由 el 属性引用的)中的模型属性。清单 18 展示了一个 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 也可以促进客户端模板的使用,这就使得我们没有必要在 JavaScript 中嵌入 HTML 代码,如 清单 18 所示。(使用模板,模板会封装视图中常见函数;只指定此函数一次即可。)Backbone 在 underscore.js(一个必须的库)中提供一个模板引擎,尽管没有必要使用该模板引擎。清单 19 中的实例使用 underscore.js HTML 模板。

清单 19. HTML 含有模板
<script id="teamTemplate" type="text/template">
    <%= name %>
</script>

清单 20 显示了另一个使用 underscore.js HTML 模板的样例。

清单 20. 使用 _.template() 函数的视图
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 中最有用且最有趣的一个功能是将 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() 方法绑定到一个模型的变更事件中。当模型发生更改时,会自动触发 render() 方法,从而节省数行代码。从 Backbone 0.5.2 开始,bind() 方法就开始接受使用第三个参数来定义回调函数的对象。(在上述示例中,当前视图是回调函数 render() 中的对象)。在 Backbone 0.5.2 之前的版本中,必须使用 underscore.js 中的 bindAll 函数,如 清单 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 对象监听事件是比较容易的。对于实现这一点,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 属性的每个项均由两部分构成:

  • 左边部分指定事件类型和触发事件的选择器。
  • 右边部分定义了事件处理函数。

在 清单 23 中,当用户通过 DIV 中的类 more 以及类 team-element 点击链接时,会调用函数 moreInfo

回页首

结束语

MVC 模式可以为大型 JavaScript 应用程序提供所需的组织化代码。Backbone 是一个 JavaScript MVC 框架,它属于轻量级框架,且易于学习掌握。模型、视图、集合和路由器从不同的层面划分了应用程序,并负责处理几种特定事件。处理 Ajax 应用程序或者 SPI 应用程序时,Backbone 可能是最好的解决方案。

时间: 2024-10-08 11:34:29

开始学习 Backbone的相关文章

学习Backbone中的一些疑惑

1,model的id,idAttribute和cid的区别? 答:cid和id都是model的唯一标识符,使用二者任意一个都可以从collection中返回一个model. cid存储了Backbone.js为model分配的客户端唯一标识,在某些情况下(比如新增数据),你的对象还没有存入数据库中,站在服务端角度,这个model还没有一个唯一标识,但是客户端为其生成了一个cid,这时,在客户端的角度,此时,这个model与其他同类的model没有什么区别,这样前端代码就可以方便的统一处理这一类m

(六)backbone - API学习 - Backbone路由

Backbone路由本质 Backbone路由分为两个大块,Router以及History用户在Router中定义相关规则,然后开启history.start进行路由监控,执行默认的回调所以,Router本身除了定义路由规则,全部调用的是Backbone.history的方法Backbone.History的主要关注点事实上是 popstate(hashChange)的回调checkUrl,无论我们触发navigate或者点击浏览器后退皆是进入此入口,再回调至Router定义的回调,从而实现相关

backbone学习时练习的简单示例

现在js MVC模式大行其道,像backbone,angularjs都是一个好的jsMVC框架.在学习backbone时,看了一下todo示例,写了一个简单的增删改查的示例,其中用到bootstrap做界面.练习一下! 列表页面 新增或修改页面 1 <!DOCTYPE html> 2 <html lang="zh-CN"> 3 <head> 4 <meta charset="utf-8"> 5 <meta htt

Backbone学习笔记一Backbone中的MVC

原文章地址http://bigdots.github.io/2015/12/01/Backbone学习笔记(一)/#more Backbone.js为复杂WEB应用程序提供模型(models).集合(collections).视图(views)的结构.其中models用于绑定键值数据和自定义事件:collections附有可枚举函数的丰富API: views可以声明事件处理函数,并通过RESRful JSON接口连接到应用程序. 下面通过实例来学习backbone的MVC.实例来自http://

Backbone.js学习之一

昨天一个我崇拜的朋友,徐飞送我一本名为<Backbone.js实战>书,让我心中狂喜,于是带着这份浓厚的兴趣,开始研究Backbone.js之路. 打开这本书的第一句话就很有哲理,“授人以鱼,不如授之以渔.”,我花了一段时间仔细的看完了的第一章节,今天通过自己所理解的, 记录下我自学的第一步.其他不多说,进入主题: 首先在学习Backbone.js之前要了解Backbone.js是什么? 在我看来它是一个处理前端MVC很好的js框架,Backbone是一个非常轻量级的javascript库,自

【Backbone】 Backbone初探

前言 在此之前研究了一段React,但是不得不承认React.Vue等MVVM框架相对于原有的Jquery来说,简直是翻天覆地的不同.它们之间的差异不仅仅体现在框架思维的不同,而是ES5到ES6的编程语言升级,还有整体构建体系的区别.Jquery仅仅代表JS作为支持语言为HTML提供更好的体验:而到React时代,JS已经反客为主,HTML甚至被JSX替换,成为其中的一小部分.不过他们之间的代差过于巨大.React体系过于抽象,在独自一人研究的前提下,学习React的曲线过于陡峭,只好转而学习B

前端编程提高之旅(六)----backbone实现todoMVC

            乐帝当年学习backbone时,最开始是看官网todoMVC的实现,后来了解到requireJS便于管理JS代码,就对官网代码做了requireJS管理.但此时乐帝感觉此时的todoMVC仍然不够简明,为了加深对MVC架构的理解,乐帝对原有appview代码进行了重构,将相关显示模块单独提取出自称view,实现view原子化.乐帝已经将这个项目上传(下载地址).     加入requireJS的目录结构:    这里主要用到templates用于放置view对应的模板,v

backbone.js1.3.3------------------history和router

backbone的router和history对象就是对window.history对象的操作. 学习backbone的router和history之前必须要学习window.history对象.html5给开发者添加了操作history的api. 这里需要了解两个概念: hash:个人理解,hash就是url最后#后面的东西,可以定位到页面的某一个位置 state:是指push到window.history中的对象. history可以在不刷新页面的情况下修改url地址,这里就使用了锚点.通过

Backbone事件模块源码分析

事件模块Backbone.Events在Backbone中占有十分重要的位置,其他模块Model,Collection,View所有事件模块都依赖它.通过继承Events的方法来实现事件的管理,可以说,它是Backbone的核心组成部分. 此外,事件模块的所有方法都挂在了全局的Backbone上,如果你的代码中需要用到自定义事件(实现观察者模式),可以直接使用它. 所以很有必要一起来研究下Backbone.Events的源码,一来学习Backbone事件模块优秀的写法和思想,二来可以更好的灵活使