BACKBONE.JS 简单入门范例


Posted in Javascript onOctober 17, 2017

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信息,显示到页面上:

<!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获得的属性:

BACKBONE.JS 简单入门范例

Model提供数据和与数据相关的逻辑。上图输出的属性是数据,代码中的fetch/parse/get/set都是对数据进行操作,其他的还有escape/unset/clear/destory,从函数名字就大致可以明白它的用途。还有很常用的validate函数,在set/save操作时用来做数据验证,验证失败会触发invalid事件:

/* 替换之前代码的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部分:

<!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条数据:

/* 替换之前代码的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实现:

<!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

/* 替换之前代码的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框架要容易掌握得多,作为入门学习也是非常不错的。

Javascript 相关文章推荐
漂亮的widgets,支持换肤和后期开发新皮肤(2007-4-27已更新1.7alpha)
Apr 27 Javascript
jquery text()要注意啦
Oct 30 Javascript
js 鼠标拖动对象 可让任何div实现拖动效果
Nov 09 Javascript
使用javascipt---实现二分查找法
Apr 10 Javascript
JQuery中操作Css样式的方法
Feb 12 Javascript
javascript实现锁定网页、密码解锁效果(类似系统屏幕保护效果)
Aug 15 Javascript
JS获取及设置TextArea或input文本框选择文本位置的方法
Mar 24 Javascript
js给selected添加options的方法
May 06 Javascript
JS实现网页右侧带动画效果的伸缩窗口代码
Oct 29 Javascript
JS表单验证方法实例小结【电话、身份证号、Email、中文、特殊字符、身份证号等】
Feb 14 Javascript
详解Vue学习笔记入门篇之组件的内容分发(slot)
Jul 17 Javascript
详解如何解决vue开发请求数据跨域的问题(基于浏览器的配置解决)
Nov 12 Javascript
JS获取一个表单字段中多条数据并转化为json格式
Oct 17 #Javascript
JS解决position:sticky的兼容性问题的方法
Oct 17 #Javascript
JS实现div模块的截图并下载功能
Oct 17 #Javascript
bootstrap模态框嵌套、tabindex属性、去除阴影的示例代码
Oct 17 #Javascript
AngularJS 控制器 controller的详解
Oct 17 #Javascript
VUE前端cookie简单操作
Oct 17 #Javascript
javascript 判断用户有没有操作页面
Oct 17 #Javascript
You might like
PHP 冒泡排序算法的实现代码
2010/08/08 PHP
mysql数据库差异比较的PHP代码
2012/02/05 PHP
php实例分享之mysql数据备份
2014/05/19 PHP
php简单分页类实现方法
2015/02/26 PHP
PHP的CURL方法curl_setopt()函数案例介绍(抓取网页,POST数据)
2016/12/14 PHP
PHP实现统计所有字符在字符串中出现次数的方法
2017/10/17 PHP
PHP 实现 JSON 数据的编码和解码操作详解
2020/04/22 PHP
Yii Framework框架开发微信公众平台示例
2020/04/26 PHP
javascript验证身份证完全方法具体实现
2013/11/18 Javascript
jquery插件star-rating.js实现星级评分特效
2015/04/15 Javascript
使用AngularJS创建单页应用的编程指引
2015/06/19 Javascript
需灵活掌握的Bootstrap预定义排版类 你精通吗?
2016/06/20 Javascript
详解如何在angular2中获取节点
2017/11/23 Javascript
微信小程序之圆形进度条实现思路
2018/02/22 Javascript
解决vue页面刷新或者后退参数丢失的问题
2018/03/13 Javascript
JavaScript实现随机点名器实例详解
2019/05/07 Javascript
微信小程序云开发之使用云函数
2019/05/17 Javascript
在Express中提供静态文件的实现方法
2019/10/17 Javascript
原生js实现下拉选项卡
2019/11/27 Javascript
[03:40]DOTA2英雄梦之声_第01期_炼金术士
2014/06/23 DOTA
跟老齐学Python之从格式化表达式到方法
2014/09/28 Python
使用Python实现一个简单的项目监控
2015/03/31 Python
对python产生随机的二维数组实例详解
2018/12/13 Python
flask/django 动态查询表结构相同表名不同数据的Model实现方法
2019/08/29 Python
使用Python将字符串转换为格式化的日期时间字符串
2019/09/01 Python
浅谈Python中文件夹和python package包的区别
2020/06/01 Python
Pycharm配置lua编译环境过程图解
2020/11/28 Python
Melissa鞋英国官方网站:Nonnon
2019/05/01 全球购物
Tessabit美国:集世界奢侈品和设计师品牌的意大利精品买手店
2020/06/29 全球购物
中专毕业生的自我鉴定
2013/12/01 职场文书
培训演讲稿范文
2014/01/12 职场文书
2014年单位植树节活动方案
2014/03/23 职场文书
小学毕业典礼主持词
2014/03/27 职场文书
电影开国大典观后感
2015/06/04 职场文书
百万英镑观后感
2015/06/09 职场文书
导游词之南京夫子庙
2019/12/09 职场文书