Backbone是前端mvc开发模式的框架。它可以让view和model相分离,让代码结构更清晰简答,开发进度加快,维护代码方便。可是,如今出了一种mvvm框架,它是下一代前端mvc开发模式的框架,表明做是Angular.js,改天有时间去研究下。如今先来研究下Backbone框架。javascript
Backbone提供了Model, Collection, View,Events,Controller(Router)。Model 用来建立数据,校验数据,绑定事件,存储数据到服务器端;View 用来展现数据。如此这般,在前端也作到了数据和显示分离。Backbone依赖于Underscore.js,这是一个有不少经常使用函数的js文件。同时依赖jQuery等库来操做DOM和Ajax请求。css
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"。属性事件会先于正常事件触发。好比:html
咱们先监听了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) {...}
}); java
var custom = new controller(); jquery
Backbone.history.start(); ajax
这时当页面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"的事件.json
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方法。模型的方法:
须要注意的是,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参数获得须要同步的数据,最后根据它们来适配你本身定义的 规则便可。
固然,你也能够将数据同步到本地数据库中,而不是服务器接口,这在开发终端应用时会很是适用。
加油!