BACKBONE.JS 简单入门范例
脚本之家 / 编程助手:解决程序员“几乎”所有问题!
脚本之家官方知识库 → 点击立即使用
11年刚开始用前端MVC框架时写过一篇文章,当时Knockout和Backbone都在用,但之后的项目全是在用Backbone,主要因为它简单、灵活,无论是富JS应用还是企业网站都用得上。相比React针对View和单向数据流的设计,Backbone更能体现MVC的思路,所以针对它写一篇入门范例,说明如下:
1. 结构上分4节,介绍Model/View/Collection,实现从远程获取数据显示到表格且修改删除;
2. 名为“范例”,所以代码为主,每节的第1段代码都是完整代码,复制粘贴就能用,每段代码都是基于前一段代码来写的,因此每段代码的新内容不会超过20行(大括号计算在内);
3. 每行代码没有注释,但重要内容之后有写具体的说明;
4. 开发环境是Chrome,使用github的API,这样用Chrome即使在本地路径(形如file://的路径)也能获取数据。
0. Introduction
几乎所有的框架都是在做两件事:一是帮你把代码写在正确的地方;二是帮你做一些脏活累活。Backbone实现一种清晰的MVC代码结构,解决了数据模型和视图映射的问题。虽然所有JS相关的项目都可以用,但Backbone最适合的还是这样一种场景:需要用JS生成大量的页面内容(HTML为主),用户跟页面元素有很多的交互行为。
Backbone对象有5个重要的函数,Model/Collection/View/Router/History。Router和History是针对Web应用程序的优化,建议先熟悉pushState的相关知识。入门阶段可以只了解Model/Collection/View。将Model视为核心,Collection是Model的集合,View是为了实现Model的改动在前端的反映。
1. Model
Model是所有JS应用的核心,很多Backbone教程喜欢从View开始讲,其实View的内容不多,而且理解了View意义不大,理解Model更重要。以下代码实现从github的API获取一条gist信息,显示到页面上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <!DOCTYPE html> <html> <head> <script type= "text/javascript" src= "https://code.jquery.com/jquery-1.11.1.js" ></script> <script type= "text/javascript" src= "http://underscorejs.org/underscore-min.js" ></script> <script type= "text/javascript" src= "http://backbonejs.org/backbone-min.js" ></script> <link href= "http://cdn.bootcss.com/bootstrap/3.1.1/css/bootstrap.min.css" rel= "external nofollow" rel= "external nofollow" rel= "external nofollow" rel= "stylesheet" > </head> <body> <table id= "js-id-gists" class= "table" > <thead><th>description</th><th>URL</th><th>created_at</th></thead> <tbody></tbody> </table> <script type= "text/javascript" > var Gist = Backbone.Model.extend({ url: 'https://api.github.com/gists/public' , parse: function (response) { return (response[0]); } }), gist = new Gist(); gist.on( 'change' , function (model) { var tbody = document.getElementById( 'js-id-gists' ).children[1], tr = document.getElementById(model.get( 'id' )); if (!tr) { tr = document.createElement( 'tr' ); tr.setAttribute( 'id' , model.get( 'id' )); } tr.innerHTML = '<td>' + model.get( 'description' ) + '</td><td>' + model.get( 'url' ) + '</td><td>' + model.get( 'created_at' ) + '</td>' ; tbody.appendChild(tr); }); gist.fetch(); </script> </body> </html> |
LINE4~8: 加载要用到的JS库。ajax请求和部分View的功能需要jQuery支持(或者重写ajax/View的功能);Backbone的代码是基于Underscore写的(或者用Lo-Dash代替);加载bootstrap.css只是因为默认样式太难看…
LINE16~22: 创建一个Model并实例化。url是数据源(API接口)的地址,parse用来处理返回的数据。实际返回的是一个Array,这里取第一个Object。
LINE24~33: 绑定change事件。还没有使用View,所以要自己处理HTML。这10行代码主要是get的用法(model.get),其他的功能之后会用View来实现。
LINE34: 执行fetch。从远程获取数据,获到数据后会触发change事件。可以重写sync方法
打开Chrome的Console,输入gist,可以看到Model获得的属性:
Model提供数据和与数据相关的逻辑。上图输出的属性是数据,代码中的fetch/parse/get/set都是对数据进行操作,其他的还有escape/unset/clear/destory,从函数名字就大致可以明白它的用途。还有很常用的validate函数,在set/save操作时用来做数据验证,验证失败会触发invalid事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /* 替换之前代码的JS部分(LINE16~34) */ var Gist = Backbone.Model.extend({ url: 'https://api.github.com/gists/public' , parse: function (response) { return (response[0]); }, defaults: { website: 'dmyz' }, validate: function (attrs) { if (attrs.website == 'dmyz' ) { return 'Website Error' ; } } }), gist = new Gist(); gist.on( 'invalid' , function (model, error) { alert(error); }); gist.on( 'change' , function (model) { var tbody = document.getElementById( 'js-id-gists' ).children[1], tr = document.getElementById(model.get( 'id' )); if (!tr) { tr = document.createElement( 'tr' ); tr.setAttribute( 'id' , model.get( 'id' )); } tr.innerHTML = '<td>' + model.get( 'description' ) + '</td><td>' + model.get( 'url' ) + '</td><td>' + model.get( 'created_at' ) + '</td>' ; tbody.appendChild(tr); }); gist.save(); |
跟之前的代码比较,有4处改动:
LINE7~9: 增加了defaults。如果属性中没有website(注意不是website值为空),会设置website值为dmyz。
LINE10~14: 增加validate函数。当website值为dmyz时,触发invalid事件。
LINE18~20: 绑定invalid事件,alert返回的错误。
LINE31: 不做fetch,直接save操作。
因为没有fetch,所以页面上不会显示数据。执行save操作,会调用validate函数,验证失败会触发invalid事件,alert出错误提示。同时save操作也会向Model的URL发起一个PUT请求,github这个API没有处理PUT,所以会返回404错误。
在Console中输入gist.set(‘description', ‘demo'),可以看到页面元素也会有相应的变化。执行gist.set(‘description', gist.previous(‘description'))恢复之前的值。这就是Model和View的映射,现在还是自己实现的,下一节会用Backbone的View来实现。
2. View
用Backbone的View来改写之前代码LINE24~33部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <!DOCTYPE html> <html> <head> <script type= "text/javascript" src= "https://code.jquery.com/jquery-1.11.1.js" ></script> <script type= "text/javascript" src= "http://underscorejs.org/underscore-min.js" ></script> <script type= "text/javascript" src= "http://backbonejs.org/backbone-min.js" ></script> <link href= "http://cdn.bootcss.com/bootstrap/3.1.1/css/bootstrap.min.css" rel= "external nofollow" rel= "external nofollow" rel= "external nofollow" rel= "stylesheet" > </head> <body> <table id= "js-id-gists" class= "table" > <thead><th>description</th><th>URL</th><th>created_at</th><th></th></thead> <tbody></tbody> </table> <script type= "text/javascript" > var Gist = Backbone.Model.extend({ url: 'https://api.github.com/gists/public' , parse: function (response) { return response[0]; } }), gist = new Gist(); var GistRow = Backbone.View.extend({ el: 'tbody' , MODEL: gist, events: { 'click a' : 'replaceURL' }, replaceURL: function () { this .MODEL.set( 'url' , 'http://dmyz.org' ); }, initialize: function () { this .listenTo( this .MODEL, 'change' , this .render); }, render: function () { var model = this .MODEL, tr = document.createElement( 'tr' ); tr.innerHTML = '<td>' + model.get( 'description' ) + '</td><td>' + model.get( 'url' ) + '</td><td>' + model.get( 'created_at' ) + '</td><td><a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" rel="external nofollow" >®</a></td>' ; this .el.innerHTML = tr.outerHTML; return this ; } }); var tr = new GistRow(); gist.fetch(); </script> </body> </html> |
LINE25: 所有的View都是基于DOM的,指定el会选择页面的元素,指定tagName会创建相应的DOM,如果都没有指定会是一个空的div。
LINE27~32: 绑定click事件到a标签,replaceURL函数会修改(set)url属性的值。
LINE33~35: View的初始化函数(initialize),监听change事件,当Model数据更新时触发render函数。
LINE36~42: render函数。主要是LINE41~42这两行,把生成的HTML代码写到this.el,返回this。
LINE44: 实例化GistRow,初始化函数(initialize)会被执行。
点击行末的a标签,页面显示的这条记录的URL会被修改成http://dmyz.org。
这个View名为GistRow,选择的却是tbody标签,这显然是不合理的。接下来更改JS代码,显示API返回的30条数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /* 替换之前代码的JS部分(LINE16~45) */ var Gist = Backbone.Model.extend(), Gists = Backbone.Model.extend({ url: 'https://api.github.com/gists/public' , parse: function (response) { return response; } }), gists = new Gists(); var GistRow = Backbone.View.extend({ tagName: 'tr' , render: function (object) { var model = new Gist(object); this .el.innerHTML = '<td>' + model.get( 'description' ) + '</td><td>' + model.get( 'url' ) + '</td><td>' + model.get( 'created_at' ) + '</td><td></td>' return this ; } }); var GistsView = Backbone.View.extend({ el: 'tbody' , model: gists, initialize: function () { this .listenTo( this .model, 'change' , this .render); }, render: function () { var html = '' ; _.forEach( this .model.attributes, function (object) { var tr = new GistRow(); html += tr.render(object).el.outerHTML; }); this .el.innerHTML = html; return this ; } }); var gistsView = new GistsView(); gists.fetch(); |
LINE2~9: 创建了两个Model(Gist和Gists),parse现在返回完整Array而不只是第一条。
LINE11~18: 创建一个tr。render方法会传一个Object来实例化一个Gist的Model,再从这个Model里get需要的值。
LINE26~34: 遍历Model中的所有属性。现在使用的是Model而不是Collection,所以遍历出的是Object。forEach是Underscore的函数。
Backbone的View更多的是组织代码的作用,它实际干的活很少。View的model属性在本节第一段代码用的是大写,表明只是一个名字,并不是说给View传一个Model它会替你完成什么,控制逻辑还是要自己写。还有View中经常会用到的template函数,也是要自己定义的,具体结合哪种模板引擎来用就看自己的需求了。
这段代码中的Gists比较难操作其中的每一个值,它其实应该是Gist的集合,这就是Backbone的Collection做的事了。
3. Collection
Collection是Model的集合,在这个Collection中的Model如果触发了某个事件,可以在Collection中接收到并做处理。第2节的代码用Collection实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | <!DOCTYPE html> <html> <head> <script type= "text/javascript" src= "https://code.jquery.com/jquery-1.11.1.js" ></script> <script type= "text/javascript" src= "http://underscorejs.org/underscore-min.js" ></script> <script type= "text/javascript" src= "http://backbonejs.org/backbone-min.js" ></script> <link href= "http://cdn.bootcss.com/bootstrap/3.1.1/css/bootstrap.min.css" rel= "external nofollow" rel= "external nofollow" rel= "external nofollow" rel= "stylesheet" > </head> <body> <table id= "js-id-gists" class= "table" > <thead><th>description</th><th>URL</th><th>created_at</th><th></th></thead> <tbody></tbody> </table> <script type= "text/javascript" > var Gist = Backbone.Model.extend(), Gists = Backbone.Collection.extend({ model: Gist, url: 'https://api.github.com/gists/public' , parse: function (response) { return response; } }), gists = new Gists(); var GistRow = Backbone.View.extend({ tagName: 'tr' , render: function (model) { this .el.innerHTML = '<td>' + model.get( 'description' ) + '</td><td>' + model.get( 'url' ) + '</td><td>' + model.get( 'created_at' ) + '</td><td></td>' return this ; } }); var GistsView = Backbone.View.extend({ el: 'tbody' , collection: gists, initialize: function () { this .listenTo( this .collection, 'reset' , this .render); }, render: function () { var html = '' ; _.forEach( this .collection.models, function (model) { var tr = new GistRow(); html += tr.render(model).el.outerHTML; }); this .el.innerHTML = html; return this ; } }); var gistsView = new GistsView(); gists.fetch({reset: true }); </script> </body> </html> |
LINE17~23: 基本跟第2节的第2段代码一样。把Model改成Collection,指定Collection的Model,这样Collectio获得返回值会自动封装成Model的Array。
LINE38: Collection和Model不同,获取到数据也不会触发事件,所以绑定一个reset事件,在之后的fetch操作中传递{reset: true}。
LINE42~45: 从Collection从遍历Model,传给GistRow这个View,生成HTML。
Collection是Backbone里功能最多的函数(虽然其中很多是Underscore的),而且只要理解了Model和View的关系,使用Collection不会有任何障碍。给Collection绑定各种事件来实现丰富的交互功能了,以下这段JS代码会加入删除/编辑的操作,可以在JSBIN上查看源代码和执行结果。只是增加了事件,没有什么新内容,所以就不做说明了,附上JSBIN的演示地址:http://jsbin.com/jevisopo/1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | /* 替换之前代码的JS部分(LINE16~51) */ var Gist = Backbone.Model.extend(), Gists = Backbone.Collection.extend({ model: Gist, url: 'https://api.github.com/gists/public' , parse: function (response) { return response; } }), gists = new Gists(); var GistRow = Backbone.View.extend({ tagName: 'tr' , render: function (model) { this .el.id = model.cid; this .el.innerHTML = '<td>' + model.get( 'description' ) + '</td><td>' + model.get( 'url' ) + '</td><td>' + model.get( 'created_at' ) + '</td><td><a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="js-remove">X</a> <a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="js-edit">E</a> </td>' return this ; } }); var GistsView = Backbone.View.extend({ el: 'tbody' , collection: gists, events: { 'click a.js-remove' : function (e) { var cid = e.currentTarget.parentElement.parentElement.id; gists.get(cid).destroy(); gists.remove(cid); }, 'click a.js-edit' : 'editRow' , 'blur td[contenteditable]' : 'saveRow' }, editRow: function (e) { var tr = e.currentTarget.parentElement.parentElement, i = 0; while (i < 3) { tr.children[i].setAttribute( 'contenteditable' , true ); i++; } }, saveRow: function (e) { var tr = e.currentTarget.parentElement, model = gists.get(tr.id); model.set({ 'description' : tr.children[0].innerText, 'url' : tr.children[1].innerText, 'created_at' : tr.children[2].innerText }); model.save(); }, initialize: function () { var self = this ; _.forEach([ 'reset' , 'remove' , 'range' ], function (e) { self.listenTo(self.collection, e, self.render); }); }, render: function () { var html = '' ; _.forEach( this .collection.models, function (model) { var tr = new GistRow(); html += tr.render(model).el.outerHTML; }); this .el.innerHTML = html; return this ; } }); var gistsView = new GistsView(); gists.fetch({reset: true }); |
Afterword
虽然是入门范例,但因为篇幅有限,有些基本语言特征和Backbone的功能不可能面面俱到,如果还看不懂肯定是我漏掉了需要解释的点,请(在Google之后)评论或是邮件告知。
Backbone不是jQuery插件,引入以后整个DOM立即实现增删改查了,也做不到KnockoutJS/AnglarJS那样,在DOM上做数据绑定就自动完成逻辑。它是将一些前端工作处理得更好更规范,如果学习前端MVC的目的是想轻松完成工作,Backbone可能不是最佳选择。如果有一个项目,100多行HTML和1000多行JS,JS主要都在操作页面DOM(如果讨厌+号连接HTML还可以搭配React/JSX来写),那就可以考虑用Backbone来重写了,它比其他庞大的MVC框架要容易掌握得多,作为入门学习也是非常不错的。
微信公众号搜索 “ 脚本之家 ” ,选择关注
程序猿的那些事、送书等活动等着你
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!
相关文章
全面解析JavaScript的Backbone.js框架中的Router路由
这篇文章主要介绍了Backbone.js框架中的Router路由功能,Router在Backbone中相当于一个MVC框架中的Controller控制器功能,需要的朋友可以参考下2016-05-05深入解析JavaScript框架Backbone.js中的事件机制
这篇文章主要介绍了JavaScript框架Backbone.js中的事件机制,其中涉及到Backbone的MVC结构及内存使用方面的很多知识,需要的朋友可以参考下2016-02-02深入解析Backbone.js框架的依赖库Underscore.js的作用
这篇文章主要介绍了深入解析Backbone.js框架的依赖库Underscore.js的作用,用过Node.js的朋友对Underscore一定不会陌生:)需要的朋友可以参考下2016-05-05详解Backbone.js框架中的模型Model与其集合collection
这篇文章主要介绍了Backbone.js框架中的模型Model与其集合collection,Backbone拥有与传统MVC框架相类似的Model与View结构,需要的朋友可以参考下2016-05-05简单了解Backbone.js的Model模型以及View视图的源码
这篇文章主要简单介绍了Backbone.js的Model模型以及View视图的源码,Backbone是一款高人气JavaScript的MVC框架,需要的朋友可以参考下2016-02-02JavaScript的Backbone.js框架的一些使用建议整理
这篇文章主要介绍了JavaScript的Backbone.js框架的一些使用建议整理,文中列的几点主要还是针对DOM方面的操作,需要的朋友可以参考下2016-02-02关于backbone url请求中参数带有中文存入数据库是乱码的快速解决办法
这篇文章主要介绍了关于backbone url请求中参数带有中文存入数据库是乱码的快速解决办法的相关资料,需要的朋友可以参考下2016-06-06
最新评论