Backbone简介
中文API:http://www.csser.com/tools/backbone/backbone.js.html
英文API:http://backbonejs.org/
Backbone是构建javascript应用程序的一个优秀的类库。他简洁、轻量级、功能实在。
backbone采用MVC模式,本身提供了模型、控制器和视图从而我们应用程序的骨架便形成。
backbone依赖于underscore,他是一个类库,提供了60多个函数处理数组操作、函数绑定,以及javascript模板机制。
模型
模型是保存应用程序数据的地方。我们可以把模型看做对应用程序原始数据的精心抽象,并且添加了一些工具函数和事件。
我们可以使用Backbone.Model的extend方法来创建Backbone模型:
var Note = Backbone.Model.extend({
defaults:{
title:’’//用于定义属性的
created_at:new Date(); //定义一个时间的对象
},
initialize: function () { }, //立即执行函数一直处于监听的状态
validate: functin () { }, //验证
});
extend的第一个参数是一个对象,他成为了模型实例的属性;
第二个参数是可选的类属性的哈希,通过多次调用extend可以生成模型的子类,他们将继承父亲所有类和实例属性:
var Note = Backbone.Model.extend({ //实例属性
instanceProperty: 'foo'}, { //类属性
classProperty: 'bar'});
assertEqual(User.instanceProperty, 'foo');
assertEqual(User.classProperty, 'bar');
当模型实例化时,他的initialize方法可以接受任意实例参数,其工作原理是backbone模型本身就是构造函数,所以可以使用new生成实例:
var Note = Backbone.Model.extend({
initialize: function (name) { //模型实例化时会立刻执行
this.set({name: name});
}
});
var note = new Note('Mickey');
assertEqual(note.get('name'), 'Mickey');
Ps: assertEqual用于判断相等
模型和属性
使用set和get方法设置获取实例的属性:
var note = new Note();
note.set({ name: 'Mickey' });
note.get('name'); //Mickey user.attributes;//{name: 'Mickey'}
我们看到其实user.arributes是一个对象字面量,我们不会直接操作他,因为我们使用get/set方法可以进行我们的验证流程。
使用clear方法清除实例的属性
var note = new Note();
note.set({ name: 'Mickey' });
note.clear(); //清除note的全部属性
判断实例是否有某个属性或者返回某个属性
var note = new Note();
note.set({ name: 'Mickey' });
note.has(‘name’) //判断实例是否有name的属性
note.get(‘name’) //返回note实例的title属性值
我们使用validate方法来校验一个实例属性,默认情况没有任何验证,若是我们需要验证的话:
var Note = Backbone.Model.extend({
validate: function (attr,options) {
if (!attr.name || attr.name.length < 3) {
return '名称不能为空或者小于3';
}
}
});
如果属性合法,validate不会理睬之,不合法可以返回错误字符串或者Error对象,校验失败get/set方法就会触发error事件:
var note = new Note();
note.bind('error', function (model, error) { //错误处理函数});
note.set({ name: 'm' });//给特定集合添加一个错误处理程序note.set({ name: 'm' }, { error: function (model, error) { } });
当然我们也可以在initializa里面添加对错误事件的处理
initialize: function () {
//当验证失败的时候立即执行该函数
this.on (‘invalid’,function ( model, error ) {
console.log ( error )
}
//其他事件实例
this.on(‘change’,function (model,options) { //当属性发生变化的时候触发该函数
console.log ( ‘实例属性发生了变化’ )
}
//当 ‘change’ 改为 ‘change:name’时 则仅当name属性发生变化时触发
}
使用hash名为default的对象来指定默认属性,在创建一个实例模型时,任何没有指定值的属性都会被设置为默认值:
var NoteModel = Backbone.Model.extend({
defaults: { name: 'Mickey'}
});
assertEqual((new Chat).get('name'), 'Mickey');
集合
在backbone中,模型实例的数据存放在多个集合中,为什么模型之间要使用独立的集合,其原因有点复杂,但在实际中我们经常这么做(虽然我还没做过)。
针对模型,可以通过扩展backbone.collection来创建一个集合:
var Collection = Backbone.Collection.extend({
model: Node //用于规定node的类型
initialize: function (){} //实例化集合时立即执行的函数
});
在上面的例子中,我们覆盖了model属性来指定与集合相关联的模型(这里是Node模型),虽然这个步骤不是必须的,但是为该集合设置一个默认的模型指向往往能派上大用场。
通常集合会包含单个模型的一个实例,而不是不同模型的多个实例。
在创建一个集合时,可以传递一个模型数组,比如backbone模型,如果定义了一个初始化实例函数,在初始化时就会调用之:
var collections = new Collection([{ name: 'Mickey' }, { name: 'Miki'}]);
另一种方法是使用add方法为集合添加模型:
collections.add({ name: 'Mickey' });
collections.add([{ name: 'Mickey' }, { name: 'Mcikey' }]);
还有一种方法就是先实例化模型再加入
var note1 = new Note ({name:’mickey’});
var note2 = new Note ({name:’Miki’});
var collections = new Collection ([note1,note2]);
当传递一个对象时,如果该对象已经存在,则默认不会覆盖,除非设置merge
//此处假设Note模型中有id属性
var collections = new Collection;
collections.add ({id:1, name:’mickey’})
collections.add ({id:1, name:’miki’}) //此时collections中并不存在miki
collections.add ({id:1, name:’miki’}, {merge:true}) //此时id为1的模型的name为//miki
一些模型的基本操作
var note1 = new Node({name:’mickey’})
var note2 = new Node({name:’miki’})
var note3 = new Node({name:’js’})
var collections = new Collection ([note1,note2])//新建一个集合
collections.remove(note1) //移除note1 实例 collections剩下note2
collections.reset([note1,note2])//覆盖所有模型 collections此时为note1,note2
collections.pop()
//删除最后一个模型 并返回该模型 此时collections为note1 push()则相反
collections.shift()
//删除第一个模型 并返回该模型 此时collections删除note1 unshift()则相反
collections.add(note3,{at:1})//将note3插入到索引号为1的位置上,也就是第二个元素
collections.set([note1,note2])
//插入覆盖模型,如果元素已经在,如果不同则合并,否则不操作。如果集合中没有该模型,则插入,如果集合中有该模型,参数中没有,则删除模型
collections.get(3) //得到id号为3的模型
collections.at(1) //得到索引号为1的模型
在为集合添加模型时会触发add事件:
collections.bind('add', function (user) { //...});//移除一个模型collections.bind('remove', function (user) { //...});//根据模型id获取模型var note = users.get('moduleId');//集合中模型被修改后出发change事件var note = new User({ name: 'Mickey' });
var collections = new Backbone.Collection();
collections.bind('change', function (rec) {//改变一个记录});
collections.add(user);
note.set({ name; 'Miki'});
控制集合内部顺序
一个集合内部元素顺序可以通过comparator方法控制,该方法的返回值便是你希望集合内部排序的规则:
var Collection = Backbone.Collection.extend({
comparator: function (user) {
return user.get('name');
}
});
返回值可以是值或者数字,具体例子我们下次有机会来试试看。
视图
backbone的视图并不是模板,而是一些控制类,他们处理模型的表现。
在很多MVC中视图一般指html或者模板,他们在控制器中处理事件和渲染,但backbone中视图:
视图代表一个UI逻辑块,负责一个简单的DOM内容
var NoteView = Backbone.View.extend({
initialize: function () { }, //初始化立即执行的函数
render: function () { } //渲染函数
});
不管视图有没有被插入页面,每个视图都知道当前的Dom元素,即this.el,el是从视图的tagName、className或者id等属性中创建的元素,没有这些值el就是空div:
var NoteView = Backbone.View.extend({
tagName: 'li',
className: 'view'});
var userView = new UserView();//结果<li class="users"></li>
若是希望视图绑定到页面上已存在的元素上,直接指定el就好(必须在页面加载后才能指定哦,不然找不到):
var NoteView = Backbone.View.extend({
el: $('.view')
});//也可以实例化一个视图时传递el(tagName、className\id)new UserView({ id: 'id' });
渲染视图
每个视图都有一个render方法,默认情况下没有任何操作,一旦视图需要重绘便会调render方法,不同的视图用不同功能的函数来覆盖函数,以处理模板渲染,并使用新的html来更新el:
var NoteView = Backbone.View.extend({
template: _.template($('#tmpt').html()), //使用的模板
render: function () { //渲染操作
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
backbone本身并不知道我们是怎么渲染视图的,我们可以自己生产元素也可以使用模板类库(一般用这个)。
在前面的代码中,我们使用了this.model的本地属性,他指向一个模型实例,在实例化时传递到视图中,模型的toJSON方法实际上返回模型未加工时的原始属性,可以在模板中使用:
new NoteView({ model: new Note });
委托事件
通过委托,backbone的视图提供了一种添加事件到el的简单快捷的方法:
var NoteView = Backbone.View.extend({
events: {
'change input[type=checkbox]': 'toggleDone',
// 绑定的事件如果改变input[type=checkbox]则触发toggleDone函数
'click .destroy': 'clear'
//如果点击.destroy 则触发clear函数
},
toggoleDone: function () { },
clear; function () {}
});
events对象为{'eventType selector': 'callback'}这种格式,selector是可选的,不提供便绑定值el上。
委托利用了事件冒泡机制,意思是可以一直触发而不管el内容是否改变(估计类似于delegate吧)。
上面的callback事件触发时,他的上下文是视图当前上下午,所以this中的this.model/el都可以使用。
绑定和上下文
事实上,每当视图的模型发生变化时,就会触发change事件,然后调用该函数,这就意味这应用程序的视图及HTML和与之对应的模型数据是同步的。
var NoteView = Backbone.View.extend({
initialize: function () {
_.bindAll(this, 'render', 'close');
this.model.bind('change', this.render);
},
close: function () { }
});
需要注意在回调函数中的上下文已经改变,Underscore提供一个函数:
_.bindAll(context, func);
他将函数名字和一个上下文绑定,bindAll保证了所有给定的函数总是在指定的上下文中被调用,因为函数上下文总在胡乱变化,这样做很有用。
模型销毁需要视图绑定delete事件,触发时删除el即可:
控制器
backbone的控制器将应用程序的状态和url的hash片段关联在一起,使url地址可分享,也可以作为书签使用。
本质上,控制器是一些路由和函数组成,当导航到这些路由时那些函数便调用:
router : {
‘notes(/page/:page)’: ’index’, //第一个page为静态的page 第二个是动态参数page
‘notes/:id’: ‘show’, //可以接收id作为show的参数 /#notes/id
‘login(/from/*from)’:’login’ //*作为一个参数,可以表示一个地址里面的多个部分
},
参数以“:”开始,然后是参数名,当路由被调用时,所有参数都会传递到他的函数,*为通配符,和参数一将和匹配的值一起传递到路由的函数中。
路由是以哈希对象中定义顺序倒叙进行解析的,来创建一个控制器吧:
var PageController = Backbone.Controller.extend({
router : {
‘notes(/page/:page)’: ’index’,
‘notes/:id’: ‘show’,
‘login(/from/*from)’:’login’
},
});