Backbone是前端mvc开发模式的框架。它能够让view和model相分离,让代码结构更清晰简答,开发进度加快,维护代码方便。但是,现在出了一种mvvm框架,它是下一代前端mvc开发模式的框架,代表作是Angular.js,改天有时间去研究下。现在先来研究下Backbone框架。

Backbone提供了Model, Collection, View,Events,Controller(Router)。Model 用来创建数据,校验数据,绑定事件,存储数据到服务器端;View 用来展示数据。如此这般,在前端也做到了数据和显示分离。Backbone依赖于Underscore.js,这是一个有很多常用函数的js文件。同时依赖jQuery等库来操作DOM和Ajax请求。

1. Backbone.Events
Events可以被添加到任何一个javascript对象中,一旦对象与Events合体,就可以自定义事件了。
var obj = {};       //js对象
_.extend(obj, Backbone.Events);     //把Backbone.Events扩展到obj对象中。这时这个对象就拥有操作事件的方法了。_是underscore.js的对象,相当于jquery.js中的$。
obj.bind('data', function(data) {
  console.log('Receive Data: ' + data);
 });
obj.trigger('data', 'I/'m an Backbone.event');      //打印Receive Data: I'm an Backbone.event
obj.unbind('data');
obj.trigger('data', 'I/'m an Backbone.event');
另外,如果事件很多,可以给事件加上命名空间,例如"change:selection"。属性事件会先于正常事件触发。比如:

我们先监听了change事件,然后再监听了change:name属性事件,但change事件(改变name的值)在触发时,总是会先触发属性事件,然后再触发change事件。如果改变的不是name的值而是其他的值,这里只会触发change事件,而不会触发change:name属性事件。

2. Backbond.Controller(新版本是Router)
Backbone提供了前端的url#fragment的路由支持,并且可以把他们绑定到Action和Event中去。
注意:在使用前端路由的功能之前,一定要调用一次Backbone.history.start()。
var controller = Backbone.Controller.extend({
     routes: {
         "": "home",
         "!/comments": "comments",
         "!/mentions": "mentions",
         "!/:uid": "profile",
         "!/:uid/following": "following",
         "!/:uid/followers": "followers",
         "!/:uid/status/:id": "status",
         "!/search/users/:query": "user_search",
         "!/search/:query": "search"
     },
  initialize: function(){...}  ,
     home: function(){...} ,
     comments: function() {...} ,
     mentions: function() {...} ,
     profile: function(a) {...} ,
     status: function(a, b) {...} ,
     following: function(a) {...} ,
     followers: function(a) {...} ,
     user_search: function(a) {...} ,
     search: function(a) {...}
});

var custom = new controller();

Backbone.history.start();

这时当页面URL HASH发生变化时,就会执行所绑定的方法。

Backbone.Controller.extend({}) 的用法。{}里面要有一个routes的哈希表,提供了路由和方法名的键值对。
所以我们看到http://shuo.douban.com/#!/comments对应着下面的comments方法。这个页面对应着“最新回复”模块。
我们还可以看到#fragment里面有!这个符号,这个是给搜索引擎识别用的,我们下次在谈。另外,还有:uid, :query, :id这些符号,这是动态的参数,uid、query、id都是这些参数的参数名,因此
http://shuo.douban.com/#!/search/users/豆瓣
就对应着"!/search/users/:query": "user_search",这个路由,继而可以用user_search()来处理。
顺便提一句,当url匹配后,会触发一个和Action名字有关的事件,比如"!/comments": "comments",如果访问了http://shuo.douban.com/#!/comments,就会触发"route:comments"的事件.

Backbone默认会通过Hash的方式来记录地址的变化,对于不支持onhashchange的低版本浏览器,会通过setInterval心跳监听Hash的变化,因此你不必担心浏览器的兼容性问题。
如果你的项目并不复杂,但你却深深喜欢它的某个特性(可能是数据模型、视图管理或路由器),那么你可以将这部分源码从Backbone中抽取出来,因为在Backbone中,各模块间的依赖并不是很强,你能轻易的获取并使用其中的某一个模块。

3. Backbone.View
View并不操作html或者css,所有的操作留给了各种各样的JS模板库。View有两个作用:1.监听事件.2.展示数据.

var view = Backbone.View.extend({
     model: User, //这个View的模型
     className: "components cross",
     template: $("#user-info-template").html(),
     initialize: function() {    //new view({})就会调用这个初始化方法
    _.bindAll(this, "render");
    this.model.bind("change", this.render)    //模型User绑定change事件
     },
     render: function() {
    var a = this.model;
    $(this.el).html(Mustache.to_html(this.template, a.toJSON()));    //使用了Mustache模板库,来解析模板,把模型User中的数据,转换成json,显示在模板中
           $(this.el).find(".days").html(function() {   //再进行细微的改变
               var b = a.get("created_at");     //取到模型User中的created_at的值
               return b;
    });
    return this ;
  }
});
在initialize中,一旦User类(模型)触发了change事件就会执行render方法,继而显示新的视图。
render方法中总是有个约定俗称的写法的。this.el是一个DOM对象,render的目的就是把内容填到this.el中。this.el会根据view提供的tagName, className, id属性创建,如果一个都没有,就会创建一个空的DIV。
更新完this.el后,我们还应该return this;这样才能继续执行下面的链式调用(如果有的话)。
我们也可以用$(view.el).remove()或者view.remove()很方便的清空DOM。
View层有一个委托事件的机制。
var view = Backbone.View.extend({
     className: "likers-manager",
  template: $("#likers-components-template").html(),   //模板HTML
  events: {
         "click .btn-more": "loadMore"    
     },
  initialize: function() {    //new view({}),就会调用
           _.bindAll(this, "render", "updateTitle", "loadOne", "loadAll", "loadMore");   //调用underscore的bingAll方法
     },
  render: function() { ... } ,
  updateTitle: function() { ... } ,
     loadOne: function(a) { ... } ,
  loadAll: function() { ... } ,
     loadMore: function(a) { ... }
});
在这里面有个events的键值对,格式为{"event selector": "callback"},其中click为事件,.btn-more是基于this.el为根的选择器,这样一旦约定好,当用户点击.btn-more的元素时,就会执行loadMore方法

4. Backbone.Model

Model 用来创建数据,校验数据,存储数据到服务器端。Models还可以绑定事件。比如用户动作变化触发 model 的 change 事件,所有展示此model 数据的 views 都会接收到 这个 change 事件,进行重绘。
最简单的定义如下:
var Game = Backbone.Model.extend({});

稍微复杂一点
var Game = Backbone.Model.extend({
  initialize: function(){
    
     },
  defaults: {
             name: 'Default title',
             releaseDate: 2011,
  }
});

initialize 相当于构造方法,初始化时调用(new时调用)
简单实用:
var portal = new Game({ name: "Portal 2", releaseDate: 2011});

var release = portal.get('releaseDate');

portal.set({ name: "Portal 2 by Valve"});

此时数据还都在内存中,需要执行save方法才会提交到服务器。
portal.save();

5. Backbone.Collection(集合)
实际上,相当于Model的集合。

需要注意的是,定义Collection的时候,一定要指定Model。 下面让我们为这个集合添加一个方法,如下:
var GamesCollection = Backbone.Collection.extend({
   model : Game,
   old : function() {
    return this.filter(function(game) {
         return game.get('releaseDate') < 2009;
     });
 }

});

集合的使用方法如下:
var games = new GamesCollection
games.get(0);

当然,也可以动态构成集合,如下:
 var GamesCollection = Backbone.Collection.extend({
   model : Game,
   url: '/games'

});

var games = new GamesCollection
games.fetch();

这边的url告诉collection到哪去获取数据,fetch方法会发出一个异步请求到服务器,从而获取数据构成集合。(fetch实际上就是调用jquery的ajax方法)

模板解析是Underscore中提供的一个方法。且Underscore是Backbone必须依赖的库。
模板解析方法能允许我们在HTML结构中混合嵌入JS代码,就像在JSP页面中嵌入JAVA代码一样:
 <ul>
  <% for(var i = 0; i < len; i++) { %>
  <li><%=data[i].title%></li>
  <% } %>
</ul>
通过模板解析,我们不需要在动态生成HTML结构时,使用拼接字符串的方法,更重要的是,我们可以将视图中的HTML结构独立管理(例如:不同的状态可能会显示不同的HTML结构,我们可以定义多个单独的模板文件,按需加载和渲染即可)。

在Backbone中,你可以使用on或off方法绑定和移除自定义事件。在任何地方,你都可以使用trigger方法触发这些绑定的事件,所有绑定过该事件的方法都会被执行,如:
var model = new Backbone.Model();
model.on('custom', function(p1, p2) {

});
 model.on('custom', function(p1, p2) {

});
model.trigger('custom', 'value1', 'value2');   //将调用以上绑定的两个方法
model.off('custom');
 model.trigger('custom');

// 触发custom事件,但不会执行任何函数,已经事件中的函数已经在上一步被移除 

如果你熟悉jQuery,你会发现它们与jQuery中的bind、unbind和trigger方法非常类似。
在单页应用中,我们通过JavaScript来控制界面的切换和展现,并通过AJAX从服务器获取数据。

可能产生的问题是,当用户希望返回到上一步操作时,他可能会习惯性地使用浏览器“返回”和“前进”按钮,而结果却是整个页面都被切换了,因为用户并不知道他正处于同一个页面中。
对于这个问题,我们常常通过Hash(锚点)的方式来记录用户的当前位置,并通过onhashchange事件来监听用户的“前进”和“返回”动作,但我们发现一些低版本的浏览器(例如IE6)并不支持onhashchange事件,只有可以使用setInterval。

Underscore还提供了一些非常实用的函数方法,如:函数节流、模板解析等。Underscore是Backbone必须依赖的库,因为在Backbone中许多实现都是基于Underscore。
相信你对jQuery一定不会陌生,它是一个跨浏览器的DOM和AJAX框架。
而对于Zepto你可以理解为“移动版的jQuery”,因为它更小、更快、更适合在移动终端设备的浏览器上运行,它与jQuery语法相同,因此你能像使用jQuery那样使用它。

服务器提供的数据接口需要兼容Backbone的规则,对于一个新的项目来说,我们可以尝试使用这套规则来构建接口。但如果你的项目中已经有一套稳定的接口,你可能会担心接口改造的风险。
没关系,我们可以通过重载Backbone.sync方法来适配现有的数据接口,针对不同的客户端环境,我们还可以实现不同的数据交互方式。例如:用户通过PC浏览器使用服务时,数据会实时同步到服务器;而用户通过移动终端使 用服务时,考虑到网络环境问题,我们可以先将数据同步到本地数据库,在合适的时候再同步到服务器。而这些只需要你重载一个方法就可以实现。

Model是Backbone中所有数据模型的基类,用于封装原始数据,并提供对数据进行操作的方法,我们一般通过继承的方式来扩展和使用它。

Backbone中的Model就像是映射出来的一个数据对象,它可以对应到数据库中的某一条记录,并通过操作对象,将数据自动同步到服务器数据库。(Collection就像映射出的一个数据集合,它可以对应到数据库中的某一张或多张关联表)。

整个Backbone的源码用一个自调用匿名函数包裹,避免污染全局命名空间。
(function() {
   Backbone.Events // 自定义事件
   Backbone.Model // 模型构造函数和原型扩展
  Backbone.Collection // 集合构造函数和原型扩展
  Backbone.Router // 路由配置器构造函数和原型扩展
  Backbone.History // 路由器构造函数和原型扩展
   Backbone.View // 视图构造函数和原型扩展
  Backbone.sync // 异步请求工具方法
  var extend = function (protoProps, classProps) { ... } // 自扩展函数
   Backbone.Model.extend = Backbone.Collection.extend = Backbone.Router.extend = Backbone.View.extend = extend; // 自扩展方法
}).call(this);
Backbone 会自动判断浏览器对 pushState 的支持,以做内部的选择。 不支持 pushState 的浏览器将会继续使用基于锚点的 URL 片段。

Events是Backbone中所有其它模块的基类,无论是Model、Collection、View还是Router和History,都继承了Events中的方法( unbind,bind,on,off,trigger,stopListening )。
我们无法直接实例化一个Events对象。
你需要注意监听函数的调用顺序,all事件总会在其它事件中的监听函数都执行完毕之后触发,同一个事件中如果绑定了多个监听函数,那它们将按照函数绑定时的顺序依次调用。

实际上我们一般并不会重载模块类的constructor方法,因为在Backbone中所有的模块类都提供了一个initialize方法,用于避免在子类中重载模块类的构造函数,当模块类的构造函数执行完成后会自动调用initialize方法。模型的方法:

  • get()方法用于直接返回数据
  • escape()方法先将数据中包含的HTML字符转换为实体形式(例如它会将双引号转换为&quot;形式)再返回,用于避免XSS攻击。
  • previous()方法接收一个属性名,并返回该属性在修改之前的状态;
  • previousAttributes()方法返回一个对象,该对象包含上一个状态的所有数据。

需要注意的是,previous()和previousAttributes()方法只能在数据修改过程中调用(即在模型的change事件和属性事件中调用)。

在调用模型的unset()和clear()方法清除模型数据时,会触发change事件,我们也同样可以在change事件的监听函数中通过previous()和previousAttributes()方法获取数据的上一个状态。

Backbone中每一个模型对象都有一个唯一标识,默认名称为id,

id应该由服务器端创建并保存在数据库中,在与服务器的每一次交互中,模型会自动在URL后面加上id,而对于客户端新建的模型,在保存时不会在URL后加上id标识,举个例子:

// 定义Book模型类
var Book = Backbone.Model.extend({
  urlRoot : '/service'
});

// 创建实例
var javabook = new Book({
  id : 1001,
  name : 'Thinking in Java',
  author : 'Bruce Eckel',
  price : 395.70
});

// 保存数据
javabook.save();

你可以抓包查看请求记录,你能看到请求的接口地址为:http://localhost/service/1001

  其中localhost是我的主机名。

  service是该模型的接口地址,是我们在定义Book类时设置的urlRoot。

  1001是模型的唯一标识(id),我们之前说过,模型的id应该是由服务器返回的,对应到数据库中的某一条记录,但这里为了能直观的测试,我们假设已经从服务器端拿到了数据,且它的id为1001。

如果同时设置了urlRoot和url参数,url参数的优先级会高于urlRoot。
(另一个细节是,url参数不一定是固定的字符串,也可以是一个函数,最终使用的接口地址是这个函数的返回值。)
javabook.save(null, {
  url: '/myservice'
});

在这个例子中,我们在调用save()方法的时候传递了一个配置对象,它包含 一个url配置项,最终抓包看到的请求地址是http://localhost/myservice。因此你可以得知,通过调用方法时传递的url参数优 先级会高于模型定义时配置的url和urlRoot参数。

模型的parse()方法默认不会对数据进行解析,因此我们只需要重载该方法,就可以适配上面的数据格式了

// 定义Book模型类
var Book = Backbone.Model.extend({
    urlRoot : '/service',
    // 重载parse方法解析服务器返回的数据
    parse : function(resp, xhr)
{
        var
data = resp.data[0];
       
return {
           
id : data.bookId,
           
name : data.bookName,
           
author : data.bookAuthor,
           
price : data.bookPrice
        }
    }
});

另外值得注意的一点是:我们常常会在数据保存成功后,对界面做一些改变。此时你可以通过许多种方式实现,例如通过save()方法中的success回调函数。

但我建议success回调函数中只要做一些与业务逻辑和数据无关的、单纯的界面展现即可(就像控制加载动画的显示隐藏),如果数据保存成功之后涉及到业务逻辑或数据显示,你应该通过监听模型的change事件,并在监听函数中实现它们。虽然Backbone并没有这样的要求和约束,但这样更有利于组织你的代码。

在Backbone中,所有与服务器交互的逻辑都定义在 Backbone.sync方法中,该方法接收method、model和options三个参数。如果你想重新定义它,可以通过method参数得到需要进行的操作(枚举值为create、read、update和delete),通过model参数得到需要同步的数据,最后根据它们来适配你自己定义的 规则即可。

当然,你也可以将数据同步到本地数据库中,而不是服务器接口,这在开发终端应用时会非常适用。

加油!

Backbone框架浅析的更多相关文章

  1. MVC框架浅析(基于PHP)

    MVC框架浅析(基于PHP) MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑.数 ...

  2. CI框架浅析(二)

    该文延续上篇文章: CI框架浅析(一) 在CI框架的核心库中,CodeIgniter.php负责加载所有需要的类库,第一个加载的是公共库 core/Common.php Common.php 负责加载 ...

  3. 【转载】DXUT11框架浅析(4)--调试相关

    原文:DXUT11框架浅析(4)--调试相关 DXUT11框架浅析(4)--调试相关 1. D3D8/9和D3D10/11的调试区别 只要安装了DXSDK,有个调试工具DirectX ControlP ...

  4. CI框架浅析

    CI框架浅析(全篇)     业余花了点时间看看CodeIgniter框架(简称CI),CI目前的稳定版本是 3.X,4.0版本已经出来了,但还在测试中,所以我分析的还是 3.x 版本. CI是一个很 ...

  5. Android Otto框架浅析

    今天要介绍的是一个Android中使用得比較多的android 事件总线 EventBus模式的一个框架Otto. Otto 官网:http://square.github.io/otto/ 一.An ...

  6. backbone框架思路

    Backbone是MVC框架.如何使用这个框架来简化前端过程呢?作为一个初学者,虽然网上有它的官方文档,但是都是讲的一些方法,思路分析的却对初学者来说,看得晕乎乎的.现在就把自己对这个框架的理解记录在 ...

  7. Android Dagger依赖注入框架浅析

    今天接触了Dagger这套android的依赖注入框架(DI框架).感觉跟Spring 的IOC差点儿相同吧.这个框架它的优点是它没有採用反射技术(Spring是用反射的),而是用预编译技术.因为基于 ...

  8. GreenDao开源ORM框架浅析

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/u011133213/article/details/37738943 Android程序开发中,避免 ...

  9. 深入java集合学习1-集合框架浅析

    前言 集合是一种数据结构,在编程中是非常重要的.好的程序就是好的数据结构+好的算法.java中为我们实现了曾经在大学学过的数据结构与算法中提到的一些数据结构.如顺序表,链表,栈和堆等.Java 集合框 ...

随机推荐

  1. C#动手实践:Kinect V2 开发(1):初步了解及环境搭建

    该分享使用的是Kinect 二代,C#做为编程语言,请知悉 Kinect是微软在2009年6月2日的E3游戏展上,正式公布的XBOX360体感周边外设.它是一种3D体感摄影机,同时它导入了即时动态捕捉 ...

  2. ACM入门

    1.给n个数字,将它们重新排序得到一个最大的数字 例子 4123 124 56 90--------------90561241235123 124 56 90 9------------990561 ...

  3. jcmd、jmc介绍

    一.jcmd命令使用 概述 在JDK 1.7之后,新增了一个命令行工具jcmd.它是一个多功能工具,可以用来导出堆,查看Java进程,导出线程信息,执行GC等. 使用示例 下面这个命令能够列出当前运行 ...

  4. 重写类的Equals以及重写Linq下的Distinct方法

    当自定义一个类的时候,如果需要用到对比的功能,可以自己重写Equals方法,最整洁的方法是重写GetHashCode()方法. 但是,这个方法只适用于对象自身的对比(如if(a==b))以及字典下的C ...

  5. 给 VS 2010 选一个好用的代码行数统计器(转)

    给 VS 2010 选一个好用的代码行数统计器 分类: Tricks2011-02-25 05:40 3891人阅读 评论(0) 收藏 举报 2010c 推荐一个VS插件,支持2005/2008/20 ...

  6. 介绍Unreal Engine 4中的接口(Interface)使用C++和蓝图

    这个教程是从UE4 Wiki上整理而来. 在C++中直接使用Interface大家应该很熟悉.只是简单先定义一个个有虚函数的基类,然后在子类中实现相应的虚函数.像这样的虚函数的基类一般概念上叫接口.那 ...

  7. source tree 推送错误解决

    fatal: The remote end hung up unexpectedly 出现这个问题是因为文件过大 解决办法: 打开git bash 输入git config --global http ...

  8. 上海敏行医学招聘物理仿真,3D图形人才

    工作职能: 1.开发医学虚拟手术中的柔体仿真引擎/图形效果 2.柔体仿真引擎.和引擎开发主工程师一起完善和改进仿真引擎的开发工作. 3.3D图形效果的改进. 职位要求: 1.本科以上学历,1年以上c+ ...

  9. Git self-learning

    ---恢复内容开始--- 使用后的总结 git config --global user.name "" #设置和查看用户名git config --global user.ema ...

  10. (Hibernate进阶)Hibernate基本映射(三)

    介绍Hibernate的经典内容:对象关系映射.主要介绍映射的基本概念,映射的分类,映射文件. 概念 ORM(Object Relational Mapping),即对象关系映射.它的作用就是在关系型 ...