2019独角兽企业重金招聘Python工程师标准>>>
第一章 Hello Backbonejs
1.1 基础概念
Backbone,英文意思是:勇气, 脊骨,但是在程序里面,尤其是在Backbone后面加上后缀js之后,它就变成了一个框架,一个js库。
Backbone.js,不知道作者是以什么样的目的来对其命名的,可能是希望这个库会成为web端开发中脊梁骨。
好了,八卦完了开始正题。
Backbone.js提供了一套web开发的框架,通过Models进行key-value绑定及自定义事件处理,通过Collections提供一套丰富的API用于枚举功能,通过Views来进行事件处理及与现有的Application通过RESTful JSON接口进行交互.它是基于jQuery和underscore的一个前端js框架。
整体上来说,Backbone.js是一个web端javascript的MVC框架,算是轻量级的框架。它能让你像写Java(后端)代码组织js代码,定义类,类的属性以及方法。更重要的是它能够优雅的把原本无逻辑的javascript代码进行组织,并且提供数据和逻辑相互分离的方法,减少代码开发过程中的数据和逻辑混乱。
在Backbonejs有几个重要的概念,先介绍一下:Model,Collection,View,Router。其中Model是根据现实数据建立的抽象,比如人(People);Collection是Model的一个集合,比如一群人;View是对Model和Collection中数据的展示,把数据渲染(Render)到页面上;Router是对路由的处理,就像传统网站通过url现实不同的页面,在单页面应用(SPA)中通过Router来控制前面说的View的展示。
通过Backbone,你可以把你的数据当作Models,通过Models你可以创建数据,进行数据验证,销毁或者保存到服务器上。当界面上的操作引起model中属性的变化时,model会触发change的事件。那些用来显示model状态的views会接受到model触发change的消息,进而发出对应的响应,并且重新渲染新的数据到界面。在一个完整的Backbone应用中,你不需要写那些胶水代码来从DOM中通过特殊的id来获取节点,或者手工的更新HTML页面,因为在model发生变化时,views会很简单的进行自我更新。
上面是一个简单的介绍,关于backbone我看完他的介绍和简单的教程之后,第一印象是它为前端开发制定了一套自己的规则,在这个规则下,我们可以像使用django组织python代码一样的组织js代码,它很优雅,能够使前端和server的交互变得简单。
在查backbone资料的时候,发现没有很系统的中文入门资料和更多的实例,所以我打算自己边学边实践边写,争取能让大家通过一系列文章能快速的用上Backbone.js。
关于backbone的更多介绍参看这个:
http://documentcloud.github.com/backbone/
http://backbonetutorials.com/
1.2 backbone的应用范围:
它虽然是轻量级框架,但是框架这东西也不是随便什么地方都能用的,不然就会出现杀鸡用牛刀,费力不讨好的结果。那么适用在哪些地方呢?
根据我的理解,以及Backbone的功能,如果单个网页上有非常复杂的业务逻辑,那么用它很合适,它可以很容易的操作DOM和组织js代码。
豆瓣的阿尔法城是一个极好的例子——纯单页、复杂的前端逻辑。
当然,除了我自己分析的应用范围之外,在Backbone的文档上看到了很多使用它的外国站点,有很多,说明Backbonejs还是很易用的。
稍稍列一下国内用到Backbonejs的站点:
1. 豆瓣阿尔法城 链接:http://alphatown.com/
2. 豆瓣阅读 链接:http://read.douban.com/ 主要用在图书的正文页
3. 百度开发者中心 链接:http://developer.baidu.com/
4. 手机搜狐直播间 链接:http://zhibo.m.sohu.com/
5. OATOS企业网盘 链接:http://app.oatos.com
1.3 学以致用
现在,我们就要开始学习Backbonejs了,我假设你没有看过我的第一版,那一版有很多很多问题,在博客上也有很多人反馈。但是如果你把那一版看明白了,这新版的教程你可以粗略的浏览一遍,不过后面新补充的实践是要自己写出来、跑起来的。
先说我们为什么要学习这新的东西呢?简单说来是为了掌握更加先进的工具。那为什么要掌握先进的工具呢?简单来说就是为了让我们能够以更合理、优雅的方式完成工作,反应到代码上就是让代码变得可维护,易扩展。如果从复杂的方向来说的话,这俩话题都够我写好几天的博客了。
学以致用,最直接有效的就是用起来,光学是没用的,尤其是编程这样的实践科学。新手最常犯的一个错误就是喜欢不停的去看书,看过了就以为会了,然后就开始疯狂的学下一本。殊不知看懂和写出来能运行是两种完全不同的状态。因此建议新手——编程新手还是踏踏实实的把代码都敲了,执行了,成功了才是。
下面直接给一个简单的Demo出来,用到了Backbonejs的三个主要模块:Views,Collection,Model。通过执行这个例子,了解这个例子的运行过程,快速对要做的东西有一个感觉,然后再逐步击破。
1.4 完整DEMO
这个demo的主要功能是点击页面上得“新手报到”按钮,弹出对话框,输入内容之后,把内容拼上固定的字符串显示到页面上。事件触发的逻辑是: click 触发checkIn方法,然后checkIn构造World对象放到已经初始化worlds这个collection中。
来看完整的代码:
the5fire.com-backbone.js-Hello World
更多教程
这里面涉及到backbone的三个部分,View、Model、Collection,其中Model代表一个数据模型,Collection是模型的一个集合,而View是用来处理页面以及简单的页面逻辑的。
动手把代码放到你的编辑器中吧,成功执行,然后修改某个地方,再次尝试。
第二章 Backbonejs中的Model实践
上一章主要是通过简单的代码对Backbonejs做了一个概括的展示,这一章开始从Model层说起,详细解释Backbonejs中的Model这个东西。
对于Model这一部分,其官网是这么说的:“Model是js应用的核心,包括基础的数据以及围绕着这些数据的逻辑:数据转换、验证、属性计算和访问控制”。这句话基本上高度概括了Model在一个项目中的作用。实际上,不仅仅是js应用,在任何以数据收集和处理的项目中Model都是很重要的一块内容。
Model这个概念在我的印象中是来自于MVC这个东西,Model在其中的作用,除了是对业务中实体对象的抽象,另外的作用就是做持久化,所谓持久化就是把数据存储到磁盘上——文件形式、数据库形式。在web端也有对应的操作,比如存入LocalStorage,或者Cookie。
在web端,Model还有一个重要的功能就是和服务器端进行数据交互,就像是服务器端的程序需要和数据库交互一样。因此Model应该是携带数据流窜于各个模块之间的东西。
下面让我们通过一个一个的实例来逐步了解Model。
先定义一个页面结构,实践时须在注释的地方填上各小节的代码
the5fire-backbone-model
2.1 最简单的对象
var Man = Backbone.Model.extend({ initialize: function(){ alert('Hey, you create me!');
}
});var man = new Man;
这个确实很简单了,只是定义了一个最基础的Model,只是实现了initialize这个初始化方法,也称构造函数。这个函数会在Model被实例化时调用。
2.2 对象属性赋值的两种方法
第一种,直接定义,设置默认值。
var Man = Backbone.Model.extend({ initialize: function(){ alert('Hey, you create me!');
},
defaults: {
name:'张三',
age: '38'
}
});var man = new Man;alert(man.get('name'));
第二种,赋值时定义
var Man = Backbone.Model.extend({ initialize: function(){ alert('Hey, you create me!');
}
});var man = new Man;
man.set({name:'the5fire',age:'10'});alert(man.get('name'));
从这个对象的取值方式可以知道,属性在一个Model是以字典(或者类似字典)的方式存在的,第一种设定默认值的方式,只不过是实现了Backbone的defaults这个方法,或者是给defaults进行了赋值。
2.3 对象中的方法
var Man = Backbone.Model.extend({ initialize: function(){ alert('Hey, you create me!');
},
defaults: {
name:'张三',
age: '38'
}, aboutMe: function(){ return '我叫' + this.get('name') + ',今年' + this.get('age') + '岁';
}
});var man = new Man;alert(man.aboutMe());
也是比较简单,只是增加了一个新的属性,值是一个function。说到这,不知道你是否发现,在所有的定义或者赋值操作中,都是通过字典的方式来完成的,比如extend Backbone的Model,以及定义方法,定义默认值。方法的调用和其他的语言一样,直接 .
即可,参数的定义和传递也一样。
2.4 监听对象中属性的变化
假设你有在对象的某个属性发生变化时去处理一些业务的话,下面的示例会有帮助。依然是定义那个类,不同的是我们在构造函数中绑定了name属性的change事件。这样当name发生变化时,就会触发这个function。
var Man = Backbone.Model.extend({ initialize: function(){ alert('Hey, you create me!'); //初始化时绑定监听
this.bind("change:name",function(){ var name = this.get("name"); alert("你改变了name属性为:" + name);
});
},
defaults: {
name:'张三',
age: '38'
}, aboutMe: function(){ return '我叫' + this.get('name') + ',今年' + this.get('age') + '岁';
}
});var man = new Man;//触发绑定的change事件,alert。man.set({name:'the5fire'});//触发绑定的change事件,alert。man.set({name:'the5fire.com'});
2.5 为对象添加验证规则,以及错误提示
var Man = Backbone.Model.extend({ initialize: function(){ alert('Hey, you create me!'); //初始化时绑定监听, change事件会先于validate发生
this.bind("change:name",function(){ var name = this.get("name"); alert("你改变了name属性为:" + name);
}); this.bind("invalid",function(model,error){ alert(error);
});
},
defaults: {
name:'张三',
age: '38'
}, validate:function(attributes){ if(attributes.name == '') { return "name不能为空!";
}
}, aboutMe: function(){ return '我叫' + this.get('name') + ',今年' + this.get('age') + '岁';
}
});var man = new Man;// 这种方式添加错误处理也行// man.on('invalid', function(model, error){// alert(error);// });//默认set时不进行验证man.set({name:''});//手动触发验证, set时会触发//man.set({name:''}, {'validate':true});//save时触发验证。根据验证规则,弹出错误提示。man.save();
2.6 和服务器进行交互,对象的保存和获取
首先需要声明的是,这个例子需要后端配合,可以在 code 目录中找到对应的py文件,需要webpy和mako这两个库。 这里需要为对象定义一个url属性,调用save方法时会post对象的所有属性到server端,调用fetch方法是又会发送get请求到server端。接受数据和发送数据均为json格式:
var Man = Backbone.Model.extend({
url:'/man/', initialize: function(){ alert('Hey, you create me!'); //初始化时绑定监听
this.bind("change:name",function(){ var name = this.get("name"); alert("你改变了name属性为:" + name);
}); this.bind("error",function(model,error){ alert(error);
});
},
defaults: {
name:'张三',
age: '38'
}, validate:function(attributes){ if(attributes.name == '') { return "name不能为空!";
}
}, aboutMe: function(){ return '我叫' + this.get('name') + ',今年' + this.get('age') + '岁';
}
});var man = new Man;;
man.set({name:'the5fire'});//会发送POST到模型对应的url,数据格式为json{"name":"the5fire","age":38}man.save();//然后接着就是从服务器端获取数据使用方法fetch([options])var man1 = new Man;//第一种情况,如果直接使用fetch方法,//那么他会发送get请求到你model的url中,//你在服务器端可以通过判断是get还是post来进行对应的操作。man1.fetch();//第二种情况,在fetch中加入参数,如下:man1.fetch({url:'/man/'});//这样,就会发送get请求到/getmans/这个url中,//服务器返回的结果样式应该是对应的json格式数据,同save时POST过去的格式。//不过接受服务器端返回的数据方法是这样的:man1.fetch({url:'/man/', success:function(model,response){ alert('success'); //model为获取到的数据
alert(model.get('name'));
},error:function(){ //当返回格式不正确或者是非json数据时,会执行此方法
alert('error');
}
});
还有一点值得一提的是关于url和urlRoot的事情了,如果你设置了url,那么你的CRUD都会发送对应请求到这个url上,但是这样有一个问题,就是delete请求,发送了请求,但是却没有发送任何数据,那么你在服务器端就不知道应该删除哪个对象(记录),所以这里又一个urlRoot的概念,你设置了urlRoot之后,你发送PUT和DELETE请求的时候,其请求的url地址就是:/baseurl/[model.id],这样你就可以在服务器端通过对url后面值的提取更新或者删除对应的对象(记录)
补充一点,就是关于服务器的异步操作都是通过Backbone.sync这个方法来完成的,调用这个方法的时候会自动的传递一个参数过去,根据参数向服务器端发送对应的请求。比如你save,backbone会判断你的这个对象是不是新的,如果是新创建的则参数为create,如果是已存在的对象只是进行了改变,那么参数就为update,如果你调用fetch方法,那参数就是read,如果是destory,那么参数就是delete。也就是所谓的CRUD ("create", "read", "update", or "delete"),而这四种参数对应的请求类型为POST,GET,PUT,DELETE。你可以在服务器根据这个request类型,来做出相应的CRUD操作。
关于Backbone.sync在后面会有如何自定义这一部分的章节。
上面服务器端的代码在 code
下可以找到,基于webpy和mako的。
第三章 Backbonejs中的Collections实践
上一节介绍了model的使用,model算是对现实中某一物体的抽象,比如你可以定义一本书的model,具有书名(title)还有书页(page_num)等属性。仅仅用一个Model是不足以呈现现实世界的内容,因此基于Model,这节我们来看collection。collection是model对象的一个有序的集合,也可以理解为是model的容器。概念理解起来十分简单,在通过几个例子来看一下,会觉得更容易理解。
3.1 关于book和bookshelf的例子
var Book = Backbone.Model.extend({
defaults : {
title:'default'
}, initialize: function(){ //alert('Hey, you create me!');
}
});var BookShelf = Backbone.Collection.extend({
model : Book
});var book1 = new Book({title : 'book1'});var book2 = new Book({title : 'book2'});var book3 = new Book({title : 'book3'});//注意这里面是数组,或者使用add//var bookShelf = new BookShelf([book1, book2, book3]);var bookShelf = new BookShelf;
bookShelf.add(book1);
bookShelf.add(book2);
bookShelf.add(book3);
bookShelf.remove(book3);//基于underscore这个js库,还可以使用each的方法获取collection中的数据bookShelf.each(function(book){ alert(book.get('title'));
});
很容易理解吧。
3.2 使用fetch从服务器端获取数据
首先要在上面的的Bookshelf中定义url,注意collection中并没有urlRoot这个属性。或者你直接在fetch方法中定义url的值,如下:
//注意这里bookShelf.url = '/books/';
bookShelf.fetch({ success:function(collection, response, options){
collection.each(function(book){ alert(book.get('title'));
});
},error:function(collection, response, options){ alert('error');
}
});
其中也定义了两个接受返回值的方法,具体含义我想很容易理解,返回正确格式(json)的数据,就会调用success方法,错误格式的数据就会调用error方法,当然error方法也看添加和success方法一样的形参。
对应的BookShelf的返回格式如下:[{'title':'book0'},{'title':'book1'}.....]
3.3 reset方法
使用这个方法的时候是要和上面的fetch进行配合的,collection在fetch到数据之后,默认情况会调用set方法(set方法向collection里面添加新model,如果该model之前存在则会合并,与此同时会触发collection的add事件),可以通过参数{reset: true}来手动触发reset,reset会整个清空collection重新添加所有model。这时你就需要在collection中定义reset方法或者是绑定reset方法。这里使用绑定演示:
var showAllBooks = function(){
bookShelf.each(function(book){ //将book数据渲染到页面的操作。
document.writeln(book.get('title'));
});
}
bookShelf.bind('reset',showAllBooks);
bookShelf.url = '/books/'; //注意这里bookShelf.fetch({ // 需要主动传递reset,才会触发reset
reset: true, success:function(collection, response, options){
collection.each(function(book){ alert(book.get('title'));
});
},error:function(collection, response, options){ alert('error');
}
});
绑定的步骤要在fetch之前进行。
3.4 发送数据到Server端
创建数据,其实就是调用collection的create方法,POST对应的Model对象(json数据)到配置好的url上。之后会返回一个model的实例,如下面代码中的onebook。
var NewBooks = Backbone.Collection.extend({
model: Book,
url: '/books/'});var books = new NewBooks;var onebook = books.create({
title: "I'm coming",
});
完整代码可以在 code 中找到, 服务器端的代码后面会介绍。
第四章 Backbonejs中的Router实践
前面介绍了Model和Collection,基本上属于程序中静态的数据部分。这一节介绍Backbone中的router,属于动态的部分,见名知意,router——路由的意思,显然是能够控制url指向哪个函数的。具体是怎么做的一会通过几个实例来看看。
在现在的单页应用中,所有的操作、内容都在一个页面上呈现,这意味着浏览器的url始终要定位到当前页面。那么一个页面中的左右的操作总不能都通过事件监听来完成,尤其是对于需要切换页面的场景以及需要分享、收藏固定链接的情况。因此就有了router,通过hash的方式(即#page)来完成。不过随着浏览器发展,大多数的浏览器已经可以通过history api来操控url的改变,可以直接使用 /page 来完成之前需要hash来完成的操作,这种方式看起来更为直观一些。下面提供过几个demo来切实体会一番。
4.1 一个简单的例子
var AppRouter = Backbone.Router.extend({
routes: { "*actions" : "defaultRoute"
}, defaultRoute : function(actions){ alert(actions);
}
});var app_router = new AppRouter;
Backbone.history.start();
需要通过调用Backbone.history.start()方法来初始化这个Router。
在页面上需要有这样的a标签:
testActions
点击该链接时,便会触发defaultRouter这个方法。
4.2 这个routes映射要怎么传参数
看下面例子,立马你就知道了
var AppRouter = Backbone.Router.extend({
routes: { "posts/:id" : "getPost", "*actions" : "defaultRoute"
}, getPost: function(id) { alert(id);
}, defaultRoute : function(actions){ alert(actions);
}
});var app_router = new AppRouter;
Backbone.history.start();
对应的页面上应该有一个超链接:
Post 120
从上面已经可以看到匹配#标签之后内容的方法,有两种:一种是用“:”来把#后面的对应的位置作为参数;还有一种是“*”,它可以匹配所有的url,下面再来演练一下。
var AppRouter = Backbone.Router.extend({
routes: { "posts/:id" : "getPost", //下面对应的链接为download gif
"download/*path": "downloadFile", //下面对应的链接为Load Route/Action View
":route/:action": "loadView", "*actions" : "defaultRoute"
}, getPost: function(id) { alert(id);
}, defaultRoute : function(actions){ alert(actions);
}, downloadFile: function( path ){ alert(path); // user/images/hey.gif
}, loadView: function( route, action ){ alert(route + "_" + action); // dashboard_graph
}
});var app_router = new AppRouter;
Backbone.history.start();
4.3 手动触发router
上面的例子都是通过页面点击触发router到对应的方法上,在实际的使用中,还存在一种场景就是需要在某一个逻辑中触发某一个事件,就像是jQuery中得trigger一样,下面的代码展示怎么手动触发router。
routes: { "posts/:id" : "getPost", "manual": "manual", "*actions": "defaultRoute",
},// 省略部分代码loadView: function( route, action ){ alert(route + "_" + action); // dashboard_graph},manual: function() { alert("call manual");
app_router.navigate("/posts/" + 404, {trigger: true, replace: true});
}
对应着在页面添加一个a标签: manual
然后点击这个链接,便会触发posts/:id对应的方法。
这里需要解释的是navigate后面的两个参数。trigger表示触发事件,如果为false,则只是url变化,并不会触发事件,replace表示url替换,而不是前进到这个url,意味着启用该参数,浏览器的history不会记录这个变动。
完整代码依然在 code
中可以找到。
第五章 Backbonejs中的View实践
前面介绍了存放数据的Model和Collection以及对用户行为进行路由分发的Router(针对链接)。这一节终于可以往页面上放点东西来玩玩了。这节就介绍了Backbone中得View这个模块。Backbone的View是用来显示你的model中的数据到页面的,同时它也可用来监听DOM上的事件然后做出响应。但是这里要提一句的是,相比于Angularjs中model变化之后页面数据自动变化的特性,Backbone要手动来处理。至于这两种方式的对比,各有优劣,可以暂时不关心。
下面依然是通过几个示例来介绍下view的功能,首先给出页面的基本模板:
the5fire-backbone-view
5.1 一个简单的view
var SearchView = Backbone.View.extend({ initialize: function(){ alert('init a SearchView');
}
});var searchView = new SearchView();
是不是觉得很没有技术含量,所有的模块定义都一样。
5.2 el属性
这个属性用来引用DOM中的某个元素,每一个Backbone的view都会有这么个属性,如果没有显示声明,Backbone会默认的构造一个,表示一个空的div元素。el标签可以在定义view的时候在属性中声明,也可以在实例化view的时候通过参数传递。
var SearchView = Backbone.View.extend({ initialize: function(){ alert('init a SearchView');
}
});var searchView = new SearchView({el: $("#search_container")});
这段代码简单的演示了在实例化的时候传递el属性给View。下面我们来看看模板的渲染。
var SearchView = Backbone.View.extend({ initialize: function(){
}, render: function(context) { //使用underscore这个库,来编译模板
var template = _.template($("#search_template").html()); //加载模板到对应的el属性中
$(this.el).html(template(context));
}
});var searchView = new SearchView({el: $("#search_container")});//这个reander的方法可以放到view的构造函数中//这样初始化时就会自动渲染searchView.render({search_label: "搜索渲染"});
运行页面之后,会发现script模板中的html代码已经添加到了我们定义的div中。
这里面需要注意的是在模板中定义的所有变量必须在render的时候传递参数过去,不然就会报错。 关于el还有一个东西叫做$el,这个东西是对view中元素的缓存。
5.3 再来看view中event的使用
页面上的操作除了可以由之前的router来处理之外,在一个view中定义元素,还可以使用event来进行事件绑定。这里要注意的是在view中定义的dom元素是指你el标签所定义的那一部分dom节点,event进行事件绑定时会在该节点范围内查找。
来,继续看代码。
var SearchView = Backbone.View.extend({
el: "#search_container", initialize: function(){ this.render({search_label: "搜索按钮"});
}, render: function(context) { //使用underscore这个库,来编译模板
var template = _.template($("#search_template").html()); //加载模板到对应的el属性中
$(this.el).html(template(context));
},
events:{ //就是在这里绑定的
//定义类型为button的input标签的点击事件,触发函数doSearch
'click input[type=button]' : 'doSearch'
}, doSearch: function(event){ alert("search for " + $("#search_input").val());
}
});var searchView = new SearchView();
自己运行下,是不是比写$("input[type=button]").bind('click',function(){})好看多了。
5.4 View中的模板
上面已经简单的演示了模板的用法,如果你用过django模板的话,你会发现模板差不多都是那么回事。上面只是简单的单个变量的渲染,那么逻辑部分怎么处理呢,下面来看下。
把最开始定义的模板中的内容换成下面这个。
<% _.each(labels, function(name) { %>
<% if(name != "label2") {%>
- <%= name %>
<% } %>
<% }); %>
下面是js代码
var SearchView = Backbone.View.extend({
el: "#search_container", initialize: function(){ var labels = ['label1', 'label2', 'label3']; this.render({labels: labels});
}, render: function(context) { //使用underscore这个库,来编译模板
var template = _.template($("#search_template").html()); //加载模板到对应的el属性中
$(this.el).html(template(context));
},
});var searchView = new SearchView();
再次运行,有木有觉得还不错,模板中使用的就基本的js语法。
总结一下,关于view中的东西就介绍这么多,文档上还有几个其他的属性,不过大体用法都一致。在以后的实践中用到在介绍。